
/*	@(#)stoa.c 1.1 94/10/31 SMI 	*/

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
	stoa - convert string to address

	If a string begins in \o or \O, the following address is octal
	"  "   "       "    " \x or \X, the following address is hex
	Otherwise, a string is considered text. Text may be quoted
	with double quotes and the C-like escapes \n, \b, \t, \v, \r, and \nnn
	(nnn = octal char) are recognized.
	A \ followed by a newline causes the newline
	to vanish. A \ followed by any other char causes any "magic" of
	any other char to disappear.

	Other escape sequences recognized are:
		\!cmd args [ \! || EOL ]
	which is replaced by the raw output of the execution of cmd.
	This may only be used in a string.

		\$cmd args [ \$ || EOL ]
	which is replaced by the output of the execution of cmd and
	is then reprocessed.

	A  NULL is returned on any error(s).
*/

/* #ident	"@(#)libns:stoa.c	1.6" */

#include <stdio.h>
#include <memory.h>
#include <malloc.h>
#include <tiuser.h>
#include <rfs/nsaddr.h>
#include <ctype.h>
#include "nslog.h"

#define	toupper(c)	(islower(c) ? _toupper(c) : (c))
#define	todigit(c)	((int)((c) - '0'))	/* char to digit */
#define	toxdigit(c)	((isdigit(c))?todigit(c):(toupper(c)-(int)'A'+10))
#define	isodigit(c)	(isdigit(c) && ((c) != '9') && ((c) != '8'))
#define	itoac(i)	(((i) > 9) ? ((char)((i)-10) + 'A'):((char)(i) + '0'))	
#define	MASK(n)		((1 << (n)) - 1)

#define	MAXRLEVEL	10	/* maximum recursion level */
#define	SBUFSIZE	128

#define	TRUE	1;
#define	FALSE	0;

char	scanbuf[SBUFSIZE];
int	sbp = 0;
int	rec = 0;	/* Recursion level */

char	sbuf[SBUFSIZE];

struct netbuf *
stoa(str, addr)			/* Return 0 for success, -1 for error */
char	*str;
struct netbuf	*addr;
{
	char	*xfer(), *prescan();

	int	myadr;		/* was netbuf struct allocated here ? */
	int	quote;		/* quoted string ? */

	myadr = FALSE;

	if (!str)
		return NULL;
	while (*str && isspace(*str))	/* leading whites are OK */
		++str;

	str = prescan(str);		/* Do all \$ ... \$ */

	if (!str || !*str) return NULL;		/* Nothing to convert */

	if (!addr) {
		if ((addr = (struct netbuf *)malloc(sizeof(struct netbuf))) == NULL)
			return NULL;
		myadr = TRUE;
		addr->buf = NULL;
		addr->maxlen = 0;
		addr->len = 0;
	}

	/* Now process the address */
	quote = 0;

	if (*str == '\\') {
		++str;
		switch (*str) {

		case 'X':	/* hex */
		case 'x':
			addr->len = dobase(++str, sbuf, HEX);
			break;

		case 'o':	/* octal */
		case 'O':
			addr->len = dobase(++str, sbuf, OCT);
			break;

		case '\0':	/* No address given!, length is 0 */
			addr->len = dostring(str, sbuf, 0);
			break;

		default:	/* \ is handled by dostring */
			addr->len = dostring(--str, sbuf, quote);
			break;
		}
	}
	else {
		if (*str == '"') {	/* quoted string */
			quote = 1;
			++str;
		}
		addr->len = dostring(str, sbuf, quote);
	}

	if (addr->len == 0) {	/* Error in conversion */
		if (myadr)
			free(addr);
		return NULL;
	}
	if ((addr->buf = xfer(addr->buf, sbuf, addr->len, addr->maxlen)) == NULL)
		return NULL;
	else
		return addr;
}


/*
	dostring:	Copy string at s to buf translating
		escaped characters and shell escapes.
	return length of string.
*/

int
dostring(s, buf, quote)		/* read in a raw address */
char	*s, *buf;
int	quote;
{
	char	*xcmd();

	int	oc, ch, len = 0;
	int	l = 0;
	char	*rout;

	while (*s) {
		if (len >= SBUFSIZE) {
			fprintf(stderr, "dostring: string too long\n");
			break;
		}
		else if (*s == '\\')
			switch(*++s) {

			case '!':	/* raw shell escape */
				if (rout = xcmd(s+1, '!', &s, &l)) {
					if (len + l < SBUFSIZE)
						memcpy(buf+len, rout, l);
					len += l;
					free(rout);
				}
				break;

			case '\n':	/* ignore newline */
				++s;
				break;

			case 'b':	/* backspace */
				buf[len++] = '\b'; s++;
				break;

			case 'n':	/* newline */
				buf[len++] = '\n'; s++;
				break;

			case 'r':	/* return */
				buf[len++] = '\r'; s++;
				break;

			case 't':	/* horiz. tab */
				buf[len++] = '\t'; s++;
				break;

			case 'v':	/* vert. tab */
				buf[len++] = '\v'; s++;

			case '0':
			case '1':
			case '2':
			case '3':
				for(oc=ch=0; (*s >= '0' && *s <= '7') && oc++ < 3; ++s) 
					ch = (ch << 3) | (*s - '0');
				buf[len++] = ch;
				break;

			case 0:		/* end of string -- terminate */
				break;

			default:	/* take the character blindly */
				buf[len++] = *s++;
				break;
			}
		else if ((quote && (*s == '"')) || (!quote && isspace(*s)))
			break;

		else
			buf[len++] = *s++;
	}
	return (len >= SBUFSIZE) ? 0 : len;
}


/*
	dobase :	converts a hex or octal ASCII string
		to a binary address. Only HEX or OCT may be used
		for type.
	return length of binary string (in bytes), 0 if error.
	The binary result is placed at buf.
*/

int
dobase(s, buf, type)	/* read in an address */
char	*s, *buf;	/* source ASCII, result binary string */
int	type;
{
	void	memcp();
	int	bp = SBUFSIZE - 1;
	int	shift = 0;
	char	*end;

	for (end = s; *end && ((type == OCT) ? isodigit(*end) :
		isxdigit(*end)); ++end) ;

	/* any non-white, non-digits cause address to be rejected,
	   other fields are ignored */

	if ((*s == 0) || (end == s) || (!isspace(*end) && *end)) {
		fprintf(stderr, "dobase: Illegal trailer on address string\n");
		buf[0] = '\0';
		return 0;
	}
	--end;

	buf[bp] = '\0';

	while (bp > 0 && end >= s) {
		buf[bp] |= toxdigit(*end) << shift;
		if (type == OCT) {
			if (shift > 5) {
				buf[--bp] = (todigit(*end) >> (8 - shift))
					& MASK(shift-5);
			}
			if ((shift = (shift + 3) % 8) == 0)
				buf[--bp] = 0;
		}
		else	/* hex */
			if ((shift = (shift) ? 0 : 4) == 0)
				buf[--bp] = 0;;
		--end;
	}
	if (bp == 0) {
		fprintf(stderr, "stoa: dobase: number to long\n");
		return 0;
	}

	/* need to catch end case to avoid extra 0's in front	*/
	if (!shift)
		bp++;
	memcp(buf, &buf[bp], (SBUFSIZE - bp));
	return (SBUFSIZE - bp);
}


/*

	atos(str, addr, type)

	convert address to ASCII form with address in hex, octal,
	or character form.
	return pointer to buffer (NULL on failure).
*/


char *
atos(str, addr, type)
char	*str;
struct netbuf	*addr;
int	type;
{
	char	*xfer();
	int	mystr = 0;	/* was str allocated here ? */
	unsigned	x_atos(), o_atos();
	void	memcp();

	char	*base;

	if (addr == NULL)
		return NULL;

	if (str == NULL)
		if ((str = malloc(SBUFSIZE)) == NULL)
			return NULL;
		else
			mystr = 1;

	switch (type) {

	case OCT:
		/* first add \o 	*/
		sbuf[0] = '\\';
		sbuf[1] = 'o';

		return xfer(str, sbuf, o_atos(sbuf+2, addr->buf, addr->len) + 2,
			mystr ? SBUFSIZE : 0);

	case HEX:
		/* first add \x 	*/
		sbuf[0] = '\\';
		sbuf[1] = 'x';

		return xfer(str, sbuf, x_atos(sbuf+2, addr->buf, addr->len) + 2,
			mystr ? SBUFSIZE : 0);

	case RRAW:
		base = xfer(str, addr->buf,
			 addr->len + 1, mystr ? SBUFSIZE : 0);
		if (base)
			base[addr->len] = '\0';	/* terminate */
		return base;

	default:
		return NULL;
	}
}


/*
	x_atos, o_atos
	return the number of bytes occupied by string + NULL */

/*
	x_atos :	convert an address string a, length s
		to hex ASCII in s
*/

unsigned
x_atos(s, a, l)
char	*s, *a;
unsigned	l;
{
	char	*b;

	b = s;
	while (l--) {
		*s++ = itoac(((*a >> 4) & MASK (4)));
		*s++ = itoac((*a & MASK(4)));
		++a;
	}
	*s = '\0';
	return (s - b + 1);
}


/*
	o_atos :	convert an address a, length l
		to octal ASCII in s
*/

unsigned
o_atos(s, a, l)
char	*s, *a;
unsigned	l;
{
	int	i, shift;
	char	*b;

	b = s;
	if (l == 0) {
		*s = '\0';
		return 0;
	}

	/* take care of partial bits and set shift factor for next 3 */

	i = l % 3;
	*s++ = itoac((*a>>(i+5)) & MASK(3-i));
	shift = 2 + i;

	while (l)
		if (shift <= 5) {
			*s++ = itoac((*a >> shift) & MASK(3));
			if (shift == 0) {
				++a;
				--l;
			}
			shift += (shift < 3) ? 5 : -3;
		}
		else {
			i = (*a & MASK(shift-5)) << (8-shift);
			i |= (*++a >> shift) & MASK(8-shift);
			*s++ = itoac(i);
			shift -= 3;
			--l;
		}
	*s++ = '\0';
	return (s - b + 1);
}


void
memcp(d, s, n)	/* safe memcpy for overlapping regions */
char	*d, *s;
int	n;
{
	while (n--)
		*d++ = *s++;
}


/* transfer block to a given destination or allocate one of the
    right size 
    if max = 0 : ignore max
*/

char *
xfer(dest, src, len, max)
char	*dest, *src;
unsigned	len, max;
{
	if (max && dest && max < len) {		/* No room */
		fprintf(stderr, "xfer: destination not long enough\n");
		return NULL;
	}
	if (!dest)
		if ((dest = malloc(len)) == NULL) {
			fprintf(stderr, "xfer: malloc failed\n");
			return NULL;
		}

	memcpy(dest, src, (int)len);
	return dest;
}

/*
	prescan:	scan through string s, expanding all \$...\$
		as shell escapes.
	Return pointer to string of expanded text.
*/

char *
prescan(s)
char	*s;
{
	int	scan();

	rec = sbp = 0;
	if (!s || !*s || !scan(s))
		return NULL;
	scanbuf[sbp] = '\0';
	return scanbuf;
}


/*
	scan:	scan through string s, expanding all \$...\$.
	(Part II of prescan)
	Return 0 if anything failed, else 1.
*/

scan(s)
char	*s;
{
	char	*xcmd();
	char	*cmd;
	int	len;
	int	esc = 0;		/* Keep lookout for \\$ */

	while (*s) {
		if (!esc && (*s == '\\' && *(s+1) == '$')) {
			if (rec++ == MAXRLEVEL) {
				fprintf(stderr, "scan: Recursion \
level past %d on shell escape\n", rec);
				return 0;
			}
			if ((cmd = xcmd(s+2, '$', &s, &len)) != NULL) {
				cmd[len] = '\0';
 				if (*cmd != '\0')
					scan(cmd);
				free(cmd);
			}
			else
				return 0;
		}

		else if (sbp == SBUFSIZE) {
			fprintf(stderr, "Overflow on shell esc expansion\n");
			return 0;
		}
		else if (sbp < SBUFSIZE)
			esc = ((scanbuf[sbp++] = *s++) == '\\');
	}
	return 1;
}


/*
	xcmd :	extract command line for shell escape and execute it
		return pointer to output of command
*/

char *
xcmd(s, ec, ps, len)
char	*s;		/* input string */
char	ec;		/* escape char ( $ or ! ) */
char	**ps;		/* address of input string pointer */
int	*len;		/* Number of bytes of output from command */
{
	FILE	*popen();
	int	pclose(), fread();

	FILE	*pfp;		/* pipe for process */
	char	*cmd;		/* command buffer */
	char	*cmdp;		/* pointer along cmd */
	char	*ocmd;		/* output of command buffer */
	int	esc = 0;	/* escaped escape shell */

	*len = 0;

	if ((cmd = cmdp = malloc(SBUFSIZE)) == NULL) {
		fprintf(stderr, "xcmd: malloc failed\n");
		return NULL;
	}

	if ((ocmd = malloc(SBUFSIZE)) == NULL) {
		fprintf(stderr, "xcmd: malloc failed\n");
		free(cmd);
		return NULL;
	}
	while (*s) {
		if (!esc && *s == '\\' && *(s+1) == ec) {
			s += 2;
			break;
		}
		else
			esc = (*cmdp++ = *s++) == '\\';
	}
	*cmdp = '\0';
	*ps = s;

	if ((pfp = popen(cmd, "r")) == NULL)
		fprintf(stderr, "xcmd: popen failed\n");
	while (fread(&ocmd[*len], 1, 1, pfp))
		if ((*len += 1) >= SBUFSIZE) {
			fprintf(stderr, "xcmd: command output too long\n");
			break;
		}
	pclose(pfp);
	free(cmd);

	return ocmd;
}
