static const char rcsid[] = "$Id: utils.c,v 1.159 2020/08/03 17:13:04 will Exp $";
#include "super.h"

/* Variables table flag -- !0 means has been created */
static int created_variables_table = 0;

/* an expandable input buffer */
struct Ebuf {
    char *buf;
    int l;
    int nalloc;
};
/* checksize of Ebuf; grow an Ebuf */
static char *checksize P__((struct Ebuf *cb, int N));
static char *grow P__((struct Ebuf *cb, int N));

/* The input, cleaned input, and variable expansion buffers */
static struct Ebuf ebuf = { NULL, 0, 0 };
static struct Ebuf ebuf_clean = { NULL, 0, 0 };
static struct Ebuf variablebuf = { NULL, 0, 0 };


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get environment variable */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
Getenv(s)
char *s;
{
    /* Like getenv(), but returns ptr to the <name> in "name=xxxx",
     * not just the xxxx.
     */
    char **envp; 
    int l;
    extern char **environ;

    if (!s)
	return (char *) NULL;
    l = strlen(s);
    for (envp=environ; *envp ; envp++)
	if (strncmp(*envp, s, l) == 0  &&  *(*envp+l) == '=')
	    return *envp;
    return (char *) NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get login directory of a user */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
getlogdir(user, buf)
char *user;
char *buf;
{
    /* Gets the login directory of the named user, and puts it into buf.
     * The "user" argument can either be a username or a uid in text form.
     * If user==NULL || *user == '\0', the current user is obtained.
     * Best if buf is MAXPATHLEN long.
     * 0 is returned on success; -1 on error.
     */
 
    struct passwd *pass;
 
    buf[0] = '\0';
    if (user != NULL && *user != '\0') {
	/* Name or uid given; use getpwentry */
	pass = getpwentry(1, user);
    } else {
	/* No user given; use current uid */
	pass = getpwuid(userinfo.orig_uid);
    }
 
    if (pass == (struct passwd *) NULL)
	return -1;
 
    (void) strcpy(buf, pass->pw_dir);
 
    return 0;
}
 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Convert string to syslog priority code */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
read_syslogcode(s, code)
char *s;
int *code;
{
    /*
     * Takes a string like "LOG_WARNING | LOG_AUTH", and returns the
     * corresponding syslog numeric value in *code.
     * The leading "LOG_" is optional, as is the case, and the separator
     * can be any combination of space, dot, and |.  Thus the following
     * are equivalent:
     * 		LOG_WARNING | LOG_AUTH
     * 		warning.auth
     * Returns:
     *	0 on success;
     *	-1 on error, with error message to Error().
     *
     * In s, the valid code separators are: whitespace, '.'  and/or "|".
     *
     */
    char *word, *xword;
    char *sep = " \t|.";

    char *p, *new_s;

    if (!(new_s = strdup(s))) {
	return Error(0, 0,
	    "Failed to malloc space for copy of s=<%s>\n", s);
    }
    for (p = new_s; *p; p++) {
	if (islower(*p)) {
	    *p = toupper(*p);
	}
    }

    *code = 0;
    for (word = strtok(new_s, sep); word; word = strtok(NULL, sep)) {
	if (!word) {
	    continue;	/* ignore empty field */
	}
	if (strncmp(word, "LOG_", 4) == 0) {
	    xword = word + 4;		/* ignore leading LOG_ */
	} else {
	    xword = word;		/* there is no leading LOG_ */
	}

	if (0) {
	    /* placeholder */
	/*
	 * **********
	 * Facilities
	 * **********
	 */
#ifdef LOG_AUTH
	} else if (strcmp(xword, "AUTH") == 0) {
	    *code |= LOG_AUTH;
#endif
#ifdef LOG_AUTHPRIV
	} else if (strcmp(xword, "AUTHPRIV") == 0) {
	    *code |= LOG_AUTHPRIV;
#endif
#ifdef LOG_CRON
	} else if (strcmp(xword, "CRON") == 0) {
	    *code |= LOG_CRON;
#endif
#ifdef LOG_DAEMON
	} else if (strcmp(xword, "DAEMON") == 0) {
	    *code |= LOG_DAEMON;
#endif
#ifdef LOG_KERN
	} else if (strcmp(xword, "KERN") == 0) {
	    *code |= LOG_KERN;
#endif
#ifdef LOG_LOCAL0
	} else if (strcmp(xword, "LOCAL0") == 0) {
	    *code |= LOG_LOCAL0;
#endif
#ifdef LOG_LOCAL1
	} else if (strcmp(xword, "LOCAL1") == 0) {
	    *code |= LOG_LOCAL1;
#endif
#ifdef LOG_LOCAL2
	} else if (strcmp(xword, "LOCAL2") == 0) {
	    *code |= LOG_LOCAL2;
#endif
#ifdef LOG_LOCAL3
	} else if (strcmp(xword, "LOCAL3") == 0) {
	    *code |= LOG_LOCAL3;
#endif
#ifdef LOG_LOCAL4
	} else if (strcmp(xword, "LOCAL4") == 0) {
	    *code |= LOG_LOCAL4;
#endif
#ifdef LOG_LOCAL5
	} else if (strcmp(xword, "LOCAL5") == 0) {
	    *code |= LOG_LOCAL5;
#endif
#ifdef LOG_LOCAL6
	} else if (strcmp(xword, "LOCAL6") == 0) {
	    *code |= LOG_LOCAL6;
#endif
#ifdef LOG_LOCAL7
	} else if (strcmp(xword, "LOCAL7") == 0) {
	    *code |= LOG_LOCAL7;
#endif
#ifdef LOG_LPR
	} else if (strcmp(xword, "LPR") == 0) {
	    *code |= LOG_LPR;
#endif
#ifdef LOG_MAIL
	} else if (strcmp(xword, "MAIL") == 0) {
	    *code |= LOG_MAIL;
#endif
#ifdef LOG_NEWS
	} else if (strcmp(xword, "NEWS") == 0) {
	    *code |= LOG_NEWS;
#endif
#ifdef LOG_RFS
	} else if (strcmp(xword, "RFS") == 0) {
	    *code |= LOG_RFS;
#endif
#ifdef LOG_USER
	} else if (strcmp(xword, "USER") == 0) {
	    *code |= LOG_USER;
#endif
#ifdef LOG_UUCP
	} else if (strcmp(xword, "UUCP") == 0) {
	    *code |= LOG_UUCP;
#endif
	/*
	 * ******
	 * Levels
	 * ******
	 */
#ifdef LOG_ALERT
	} else if (strcmp(xword, "ALERT") == 0) {
	    *code |= LOG_ALERT;
#endif
#ifdef LOG_CRIT
	} else if (strcmp(xword, "CRIT") == 0) {
	    *code |= LOG_CRIT;
#endif
#ifdef LOG_DEBUG
	} else if (strcmp(xword, "DEBUG") == 0) {
	    *code |= LOG_DEBUG;
#endif
#ifdef LOG_EMERG
	} else if (strcmp(xword, "EMERG") == 0) {
	    *code |= LOG_EMERG;
#endif
#ifdef LOG_ERR
	} else if (strcmp(xword, "ERR") == 0) {
	    *code |= LOG_ERR;
#endif
#ifdef LOG_INFO
	} else if (strcmp(xword, "INFO") == 0) {
	    *code |= LOG_INFO;
#endif
#ifdef LOG_NOTICE
	} else if (strcmp(xword, "NOTICE") == 0) {
	    *code |= LOG_NOTICE;
#endif
#ifdef LOG_WARNING
	} else if (strcmp(xword, "WARNING") == 0) {
	    *code |= LOG_WARNING;
#endif
	} else {
	    free(new_s);
	    return Error(0, 0, "$$\n\tUnknown syslog code <%s>\n", word);
	}
    }
    free(new_s);
    return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copies in to out, prefixing with "^" and suffixing with "$"
 * if these are missing.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
anchor(in, out)
char *in;
char *out;
{
    void re_anchor P__((char *, char *));
    if (need_re_anchor)
	re_anchor(in, out);
    else
	(void) strcpy(out, in);
}

void
re_anchor(in, out)
char *in;
char *out;
{
    int i;
    i = (*in != '^');
    if (i)
	out[0] = '^';
    (void) strcpy(out+i, in);
    i = strlen(out);
    if (out[i-1] != '$')
	out[i++] = '$';
    out[i] = '\0';
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Grow an expandable buffer */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char *
grow(cb, nb)
struct Ebuf *cb;
int nb;			/* amount to grow, bytes */
{
    if (cb->buf)
	cb->buf = realloc(cb->buf, cb->nalloc += nb);
    else
	cb->buf = malloc(cb->nalloc += nb);

    return cb->buf;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Grow buffer if less than N bytes free */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char *
checksize(cb, N)
struct Ebuf *cb;
int N;
{
    if (cb->nalloc - cb->l  <  N)
	return grow(cb, 2*N);
    else
	return cb->buf;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Check if string s1 ends with string s2 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char
*ends(s1, s2)
char *s1, *s2;
/* If s1 ends with string s2, a pointer to the ending of s1 is returned;
 * else null
 */
{
    int l1, l2;
    l1 = strlen(s1);
    l2 = strlen(s2);
    if (l1 < l2)
	return NULL;
    else if (strcmp(s1+l1-l2, s2) == 0)
	return s1+l1-l2;
    else
	return NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copy the input buffer to outbuf (default clean_buf),
 * cleaning out comments and the backslash-newline-whitespace parts; the
 * latter become plain whitespace if the backslash is preceded by letter,
 * digit, or underscore; otherwise, they are deleted.
 * Comments are also deleted.
 * Return a ptr to the string in the cleaned buffer.

 * Returns null pointer on malloc error.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
clean_buf(buf, outbuf)
char *buf;		/* input buffer */
char *outbuf;		/* output buffer; NULL means to use the
			 * ebuf_clean buffer.  If non-null, you must make
			 * sure it's >= buf in size.
			 */
{
    register char *s, *t;
    char inquote = '\0';

    if (outbuf) {
	t = outbuf;

    } else {
	if (!(t = outbuf = checksize(&ebuf_clean, ebuf.nalloc))) {
	    Error(1, 0,
		"$$\n\tFailed to malloc space for clean copy of input text\n");
	    return NULL;
	}
    }

    /* Copy s (input buffer) to t (clean input buffer);
     * delete comments, delete backslash-newline-whitespace
     * or replace w/ ' '
     */
    for (s = buf; *s; ) {
	/* Assert not in comment */
	if (*s == '\\' && *(s+1) == '\n') {
	    /* At continued line; skip over backslash-newline-whitespace;
	     * make it a blank if following digit,letter,_  .
	     */
	    if ( (s > buf) && (isalnum(*(s-1)) || *(s-1) == '_') )
		*t++ = ' ';
	    s += 2;
	    while (isspace(*s))
		s++;
	    /* s has been left positioned at next character to process */

	} else if (inquote) {
	    /* In a quote */
	    if (*s == inquote) {
		/* end of quote */
		inquote = '\0';
	    }
	    *t++ = *s++;

	} else if (my_qm[*(unsigned char *)s]) {
	    /* Begin quote */
	    inquote = *t++ = *s++;

	} else if (my_cc[*(unsigned char *)s]) {
	    /* Begin comment; skip over until EOL */
	    char last_noncmt = (s > buf) ? *(s-1) : '\0';

	    while (*s && *s != '\n')
		s++;
	    if (*s && *(s-1) == '\\') {
		/* At continued line.  Ignore comment part, and
		 * look at last non-comment char before
		 * backslash-newline-whitespace; then process as above.
		 */
		if ( (s > buf) && (isalnum(last_noncmt) || last_noncmt == '_') )
		    *t++ = ' ';
		s++;
		while (isspace(*s))
		    s++;
	    }
	    /* s has been left positioned at next character to process */

	} else {
	    /* Ordinary character */
	    *t++ = *s++;
	    /* s has been left positioned at next character to process */
	}
    }
    *t = '\0';

    return outbuf;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Do fgets to get one logical line: join lines that are terminated
 * with backslash-newline.  Don't discard backslash or newline (so that
 * we can print the exact text, if desired).
 * The result is stored in "ebuf" and a pointer to the string
 * is returned.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
fillbuffer(fp, all_indented, nl)
FILE *fp;
int *all_indented;	/* Returned !0 if continued lines are all indented */
int *nl;		/* Returned with number of lines read in */
{
    char c, *s;
    ebuf.l = 0;	/* clear the extensible buffer */

    /* Collect lines until we have a non-zero buffer (which happens with
     * the first line, of course) and it isn't terminated "\\\n".
     */

    if (nl) *nl = 0;
    *all_indented = 1;
    UNTIL(ebuf.l && !(s=ends(ebuf.buf, "\\\n"))) {
	
	if (!checksize(&ebuf, 1024)) {
	    /* Needed to, but couldn't increase the allocated space */
	    return NULL;
	}
	if (!fgets(ebuf.buf+ebuf.l, ebuf.nalloc - ebuf.l, fp))
	    return NULL;
	c = *(ebuf.buf + ebuf.l) ;
	if (nl)
	    (*nl)++;
	if (ebuf.l != 0 && !(isspace(c) || c == '#')) {
	    /* Continued line not indented. */
	    *all_indented = 0;
	}
	ebuf.l += strlen(ebuf.buf+ebuf.l);
    }
    return ebuf.buf;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Open a file; chain it to the previous opened list.
 * Returns NULL pointer on malloc error, stat error, fopen error,
 *	ownership error, etc.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
FileList *
file_open(parent, name, allow_missing, req_uid, req_gid)
FileList *parent;	/* null if this is head file */
char *name;		/* file to open */
int allow_missing;	/* silently ignore missing files if parent != null. */
uid_t *req_uid;		/* NULL if default, else ptr to required uid */
gid_t *req_gid;		/* NULL if default, else ptr to required gid;
			 * this group is also allowed write permission.
			 */
{
    char *s;

    FileList *fl;
    struct stat st;
    struct passwd *pass;
    int orig_uid, euid;

    fl = (FileList *) malloc(sizeof(FileList));
    if (!fl) {
	Error(0, 0,
	"$$Failed to malloc list member space for include file <%s>\n", name);
	return NULL;
    }

    if (!(fl->givenname = malloc(strlen(name)+1)) ) {
	Error(0, 0, "$$Failed to malloc space for filename <%s>\n", name);
	return NULL;
    }
    strcpy(fl->givenname, name);

    if (*name != '/' && parent != NULL) {
	/* it must be relative to the superfile directory */

	int l1, l2;

	s = strrchr(superfile, '/');
	if (s) {
	    l1 = s + 1 - superfile;
	    l2 = strlen(name);
	    if (!(fl->fullname = malloc(l1+l2+1)) ) {
		Error(0, 0,
		    "$$Failed to malloc space for expanded filename of <%s>\n",
		    name);
		return NULL;
	    }

	    sprintf(fl->fullname, "%.*s%s", l1, superfile,
			(strncmp("./", name, 2) == 0) ? name+2 : name);
	} else {
	    fl->fullname = fl->givenname;
	}
    } else {
	/* accept the name as is */
	fl->fullname = fl->givenname;
    }

    if (debug) {
	if (parent == NULL) {
	    fprintf(stderr, "\tOpening file %s\n", fl->fullname);
	} else {
	    fprintf(stderr, "\tLine %d: opening include file %s\n",
		parent->line, fl->fullname);
	}
    }

    /*
     * Handle missing-file case.
     */
    if (stat(fl->fullname, &st) == -1) {
	if (errno == ENOENT && allow_missing) {
	    if (debug)
		fprintf(stderr, "\tIgnoring missing file %s\n", fl->fullname);
	} else {
	    Error(1, 0, "Couldn't stat super.tab %sfile `%s': ",
		    parent == NULL ? "" : "include ", fl->fullname);
	}
	if (fl->fullname != fl->givenname)
	    free(fl->fullname);
	free(fl->givenname);
	free(fl);
	if (errno == ENOENT && allow_missing) {
	    return parent;
	} else {
	    return NULL;
	}
    }

    /*
     * Check file write permissions.
     */
    if (st.st_mode & S_IWOTH) {
	/*
	 * World-writable is never ok.
	 */
	Error(0, 0, "super.tab %sfile `%s' is world-writable.  Bailing out.\n",
			parent == NULL ? "" : "include ", fl->fullname);
	if (debug || it_came_from_cmdline) {
	    Error(0, 0,
	      "If this weren't debug mode, super would have quit here.\n");
	} else {
	    return NULL;
	}

    } else if (!req_gid && (st.st_mode & S_IWGRP)) {
	/*
	 * No group was specified, but it's group-writable!
	 */
	Error(0, 0, "super.tab %sfile `%s' is group-writable.  Bailing out.\n",
			parent == NULL ? "" : "include ", fl->fullname);
	if (debug || it_came_from_cmdline) {
	    Error(0, 0,
	      "If this weren't debug mode, super would have quit here.\n");
	} else {
	    return NULL;
	}
    }

    if (!parent) {
	/* This is the master file.  Extract its owner's name for use
	 * in the SUPER_OWNER variable.
	 */
	pass = getpwuid(st.st_uid);
	if (!pass) {
	    Error(0, 0, "$$\n\tSuper.tab file `%s' owner uid is %d, \
which has not passwd entry!\n", fl->fullname, st.st_uid);
	    return NULL;
	}
	if (add_variable("SUPER_OWNER", pass->pw_name) == -1)
	    return NULL;
	if (add_variable("SUPER_HOME", pass->pw_dir) == -1)
	    return NULL;
    }

    /*
     * Check file ownership.
     */
    euid = geteuid();
    orig_uid = userinfo.orig_uid;
    if (req_uid && req_gid) {
	/*
	 * File's uid/gid must match the required uid/gid.
	 */
	if (st.st_uid != *req_uid || st.st_gid != *req_gid) {
	    Error(0, 0, "super.tab %sfile `%s' needs to have (uid=%d, \
gid=%d), but is uid=%d, gid=%d\n",
		parent == NULL ? "" : "include ", fl->fullname,
		    *req_uid, *req_gid, st.st_uid, st.st_gid);
	    if (debug || it_came_from_cmdline) {
		Error(0, 0,
		    "If this weren't debug mode, super would quit here.\n");
	    } else {
		return NULL;
	    }
	}

    } else if (req_uid) {
	/*
	 * File's uid must match the required uid.
	 */
	if (st.st_uid != *req_uid) {
	    Error(0, 0,
		"super.tab %sfile `%s' needs to have uid=%d, but has uid=%d\n",
		parent == NULL ? "" : "include ", fl->fullname,
		    *req_uid, st.st_uid);
	    if (debug || it_came_from_cmdline) {
		Error(0, 0,
		    "If this weren't debug mode, super would quit here.\n");
	    } else {
		return NULL;
	    }
	}

    } else if (req_gid) {
	/*
	 * File's gid must match the required gid.
	 */
	if (st.st_gid != *req_gid) {
	    Error(0, 0,
		"super.tab %sfile `%s' needs to have gid=%d, but has gid=%d\n",
		    parent == NULL ? "" : "include ", fl->fullname,
			*req_gid, st.st_gid);
	    if (debug || it_came_from_cmdline) {
		Error(0, 0,
		    "If this weren't debug mode, super would quit here.\n");
	    } else {
		return NULL;
	    }
	}

    } else if (euid == 0 || orig_uid == 0) {
	/* Being run by root, using default rules: the super.tab file
	 * must be owned by root.
	 */
	if (st.st_uid != 0) {
	    Error(0, 0, "super.tab %sfile `%s' isn't owned by root, \n\
\tbut we are being run as or by root.  Bailing out.\n",
		parent == NULL ? "" : "include ", fl->fullname);
	    if (debug || it_came_from_cmdline) {
		Error(0, 0,
		  "If this weren't debug mode, super would have quit here.\n");
	    } else {
		return NULL;
	    }
	}

    } else if (orig_uid != userinfo.caller.pw_uid) {
	/*
	 * Real uid has changed -- this happens when the command is
	 * like "user:somecommand", in which case the file's owner
	 * had better be "user".
	 *
	 */
	if (st.st_uid != orig_uid) {
	    Error(0, 0, "super.tab %sfile `%s' isn't owned by uid %d.\n",
		parent == NULL ? "" : "include ", fl->fullname, orig_uid);
	    if (debug || it_came_from_cmdline) {
		Error(0, 0,
		    "If this weren't debug mode, super would quit here.\n");
	    } else {
		return NULL;
	    }
	}
    }

    if ((fl->fp = fopen(fl->fullname, "r")) == NULL) {
	Error(1, 0, "$$\n\tCouldn't open super.tab %sfile `%s': ",
		parent == NULL ? "" : "include ", fl->fullname);
	return NULL;
    }

    fl->line = 1;
    fl->nl = 0;
    fl->prev = parent;

    return fl;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Close a file; return previous in list, or NULL when all done */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
FileList *
file_close(curr)
FileList *curr;
{
    FileList *parent;

    if (!curr)
	return (FileList *) NULL;

    parent = curr->prev;

    if (debug) {
	fprintf(stderr, "\tClosing %sfile %s.\n",
		parent == NULL ? "" : "include ", curr->fullname);
	if (parent)
	    fprintf(stderr, "\tReturning to %s file %s, line %d\n",
		parent->prev == NULL ? "top-level" : "include ",
		parent->fullname, parent->line);
    }

    fclose(curr->fp);

    if (curr->fullname != curr->givenname)
	free(curr->fullname);
    free(curr->givenname);
    free(curr);

    return parent;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Initialize an StrArray */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
StrInit(a)
StrArray *a;
{
    a->n = 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Get address of string element of StrArray */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
StrEltGetPtr(a, ielt)
StrArray *a;	/* StrArray with element of interest */
int ielt;	/* Element to return */
    /* Returns NULL if no such element (or if unused) */
{
    if (ielt >= 0 && ielt < a->n && a->str[ielt].used)
	return a->str[ielt].s;
    else
	return NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Put string into element of StrArray.
 * Returns -1 on malloc/realloc failure, 0 otherwise.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
StrEltCpy(a, ielt, str)
StrArray *a;	/* StrArray with element to fill */
int ielt;	/* Element to be filled */
char *str;	/* what to put into ielt'th place; if NULL, elt is "unused". */
{
    int l;
    l = (str) ? strlen(str) : 0;

    if (StrNalloc(a, ielt) == -1)
	return -1;

    a->str[ielt].used = 1;
    if (a->str[ielt].n == 0) {
	a->str[ielt].s = malloc(l+1);
    } else if (a->str[ielt].n < l+1) {
	a->str[ielt].s = realloc(a->str[ielt].s, l+1);
	a->str[ielt].n = l+1;
    }
    if (!a->str[ielt].s) {
	a->str[ielt].n = 0;
	return -1;
    }
    if (str) {
	strcpy(a->str[ielt].s, str ? str : "");
    } else {
	a->str[ielt].used = 0;
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns number of elements of StrArray */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
StrNElts(a)
StrArray *a;
{
    return a->n;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Return last index of in-use elements of StrArray */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
StrLastInUse(a)
StrArray *a;
{
    int i;
    int last;

    for (i=0, last = -1; i < a->n; i++) {
	if (a->str[i].n && a->str[i].used)
	    last = i;
    }

    return last;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Marks all elements of StrArray as unused */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
StrEltsUnused(a)
StrArray *a;
{
    int i;
    for (i=0; i < a->n; i++)
	a->str[i].used = 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Makes StrArray large enough to have an ielt'th element.
 * Returns -1 on malloc/realloc failure, 0 otherwise.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
StrNalloc(a, ielt)
StrArray *a;	/* StrArray that must have an ielt'th element */
int ielt;
{
    int i;

    if (ielt >= a->n) {
	/* Have to increase the new space */
	unsigned int new_n;
	if (a->n == 0) {
	    new_n = (ielt < 4) ? 4 : 2*ielt ;
	    a->str = (CountedString *) malloc(sizeof(CountedString) * new_n);
	} else {
	    new_n = 2*ielt;
	    a->str = (CountedString *)
		    realloc((void *) a->str, sizeof(CountedString) * new_n);
	}
	if (!a->str)
	    return -1;
	for (i=a->n; i < new_n; i++)
	    a->str[i].n = 0;
	a->n = new_n;
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copy StrArray src to StrArray dst, starting at element src_ielt
 * in the src array, and at element dst_ielt in the dst array;
 * copy nelt elements.
 * If there are fewer than (src_ielt + nelt) elements, silently stop
 * copying at the end of the src array.
 * Returns -1 on malloc/realloc failure, 0 otherwise.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
StrBulkCpy(dst, dst_ielt, src, src_ielt, nelt)
StrArray *dst;	/* dstination array */
int dst_ielt;	/* starting element to fill */
StrArray *src;	/* sourced array */
int src_ielt;	/* starting element to copy from */
int nelt;	/* number of elements to copy */
{
    int i;
    char *s;

    if ((nelt + src_ielt) > StrNElts(src)) {
	/* not that many src elements */
	nelt = StrNElts(src) - src_ielt;
    }
    /* make sure dst has enough space */
    if (StrNalloc(dst, (dst_ielt + nelt)) != 0) {
	return -1;	/* can't make dst sufficiently large! */
    }

    for (i = 0; i < nelt; i++) {
	s = StrEltGetPtr(src, i + src_ielt);
	if (StrEltCpy(dst, i + dst_ielt, s) != 0) {
	    return -1;	/* copy failed */
	}
    }
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 * Free everything in an ArgRangePat list.
 * Don't free the pointer to the head.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
ARfree(
    ArgRangePat *head)
{
    ArgRangePat *arp, *next;
    if (!head) {
	return;
    }

    for (arp = head->next ; arp; arp = next) {
	next = arp->next;
	if (arp->pat) {
	    free(arp->pat);
	}
	free(arp);
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 * Insert a new string at the head of an arglist.
 * Return 0 on success, -1 on malloc failure.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
ARinsert(
    ArgRangePat *head,
    int arg1,
    int arg2,
    char *pat)
{
    ArgRangePat *arp;
    arp = malloc(sizeof(ArgRangePat));
    if (!arp) {
	return -1;
    }
    arp->pat = malloc(strlen(pat)+1);
    if (!arp->pat) {
	return -1;
    }
    strcpy(arp->pat, pat);
    arp->arg1 = arg1;
    arp->arg2 = arg2;
    arp->next = head->next;
    head->next = arp;
    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 * Walk an ArgRangePat list, finding the next pattern that applies
 * to the iarg'th argument.  If the start'th argument applies,
 * return it.  Stop if we reach an empty pattern.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
ArgRangePat *
ARnext(
    ArgRangePat *start,
    int iarg)
{
    ArgRangePat *arp;
    for (arp=start; arp; arp = arp->next) {
	if (!arp->pat || !arp->pat[0]) {
	    /* Stop if pattern is empty */
	    return NULL;
	} else if (arp->arg1 <= iarg  &&  iarg <= arp->arg2) {
	    return arp;
	}
    }
    /* input was null ptr, or ran out of list */
    return NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copy up to n-1 characters into "to"; then null-terminate.
 * Return 1 if all characters fitted into "to", else return 0.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
stringcopy(to, from, n)
char *to;       /* buffer to copy into */
char *from;     /* string to copy */
int n;          /* size of to buffer */
{
    int l = strlen(from);
    if (l >= n) {
	strncpy(to, from, n-1);
	to[n-1] = '\0';
	return 0;
    } else {
	strcpy(to, from);
	return 1;
    }
    /* NOTREACHED */
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Opens the logfile. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
opensuperlog()
{
    extern FILE *error_logfile;	/* to tell Error() where the log is */

    close_writer();		/* in case there is already one running */

    if (debug || it_came_from_cmdline) {
	Error(0, 0,
	  "If this weren't debug mode, \
super would try to open as log file \"%.500s\"\n", globalinfo.log.filename);
	return;
    }
    if (*globalinfo.log.filename == '\0') {
	Error(0, 0, "opensuperlog(): logfile name is (nil)\n");
	return;
    }

    globalinfo.log.fp = open_writer(globalinfo.log.user,
				globalinfo.log.filename, &globalinfo.log.pid);

    error_logfile = globalinfo.log.fp;	/* ...so Error() writes here too */
    return;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    /* In order to implement the loguser=xxx option, we (1) create a pipe,
     * (2) fork, in the child setuid to loguid; (3) child opens logfile;
     * (4) child copies from pipe to logfile.  Meanwhile, we return a pointer
     * to a stream to the pipe as the log stream seen by the parent program.
     * This allows us to implement a special uid for the logfile writer,
     * without needing the operating system to offer saved uid's or
     * interprocess file-descriptor passing, etc.
     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
FILE *
open_writer(user, filename, pid_p)
char *user;
char *filename;
pid_t *pid_p;	/* filled with pid of created logger */
{
    FILE *fp = NULL;
    int fildes[2];
    pid_t child;

    if (pipe(fildes) == -1) {
	(void) Error(1, 0, "Failed to created pipe for logfile; no logging: ");
	return NULL;
    }
    child = fork();
    if (child == -1) {
	(void) Error(1, 0, "Failed to create child for logfile; no logging: ");
	return NULL;

    } else if (child > 0) {
	/* In parent -- close read side, and aim logstream at write side */
	if (pid_p)
	    *pid_p = child;
	(void) close(fildes[0]);
	if (!(fp = fdopen(fildes[1], "w"))) {
	    (void) Error(1, 0,
		"failed to fdopen logfile pipe writer; no logging: ");
	    (void) close(fildes[1]);
	    return NULL;
	}

    } else if (child == 0) {
	/* In child.  Open log file and copy from pipe to log. */
	FILE *input;
	char text[2000];
	(void) close(fildes[1]);
	if (!(input = fdopen(fildes[0], "r"))) {
	    (void) Error(1, 0,
		"failed to fdopen logfile pipe reader; no logging: ");
	    (void) close(fildes[1]);
	    exit(1);
	}
	if (user && *user != '\0') {
	    stringcopy(localinfo.user, user, sizeof(localinfo.user));
	    *localinfo.euser = '\0';
	    *localinfo.egroup = '\0';
	    *localinfo.group = '\0';
	    *localinfo.u_g = '\0';
	    if (set_u_g() == -1) {
		(void) Error(1, 0,
		"failed to setuid %s before opening logfile; no logging: ",
		globalinfo.log.user);
		exit(1);
	    }
	}
	if (!(fp = fopen(globalinfo.log.filename, "a"))) {
	    if (user && *user != '\0')
		(void) Error(1, 0,
		    "failed to open logfile `%s' using uid `%s': ",
		    globalinfo.log.filename, localinfo.user);
	    else
		(void) Error(1, 0,
		    "failed to open logfile `%s': ", globalinfo.log.filename);
	    exit(1);
	}
	while (fgets(text, sizeof(text), input)) {
	    if (fputs(text, fp) == EOF)
		(void) Error(1, 0, "fputs to logfile failed: ");
	}
	(void) fclose(fp);
	exit(0);
    }
    return fp;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Closes the logfile stream, then calls wait(). */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

void
close_writer()
{
    pid_t pid;

    if (!globalinfo.log.fp && globalinfo.log.pid == -1)
	return;

    if (globalinfo.log.fp) {
	if (fclose(globalinfo.log.fp) == EOF)
	    Error(1, 0, "failed to close globalinfo.log.fp: ");
	globalinfo.log.fp = NULL;
    }

    if (globalinfo.log.pid != -1) {
	while ((pid = wait((int *) NULL)) > 0 && pid != globalinfo.log.pid) {
	    Error(0, 0, "wait() surprised! close_writer() received pid %d;\n\t\
expected logger pid = %d; waiting for correct pid...\n",
			pid, globalinfo.log.pid);
	}
	if (pid == -1)
	    Error(1, 0, "while waiting for logger process to exit");
	globalinfo.log.pid = -1;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    /* Construct a host-unique directory name:
     * xyz.home.caltech.edu -> prefix/xyz.home.caltech.edu/user
     * If hostname is empty string, file is prefix/user.

     * WARNING: the hostname used is that from gethostname().
     * Note that this is not necessarily unique across
     * internet domains, since it is frequently not a
     * fully-qualified domain name.  Therefore you should NOT
     * share the timestamp directory outside the local domain.
     *

     * *err is returned with an errno value, if there was an error
     * for which there's a relevant errno.  Otherwise it's set to 0.
     *
     * If msgbuf is non-null pointer, then it's *assumed* long enough
     * for error message (1000 chars is enough), and error messages
     * are printed into it; otherwise, error messages are Error()'d.
     *
     */

    /* returns NULL on error, constructed path otherwise */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
makedirname(prefix, hostname, file, err, msgbuf)
char *prefix, *hostname, *file;
int *err;
char *msgbuf;
{
    int l;
    char *s, str[MAXPATHLEN];

    *err = 0;
    l = strlen(prefix) + 1 + strlen(hostname) + 1;
    if (l >= MAXPATHLEN) {
	if (msgbuf) {
	    sprintf(msgbuf,
	"Can't create directory <%.500s...>: it would exceed MAXPATHLEN = %d\n",
	    prefix, MAXPATHLEN);
	} else {
	    Error(0, 0,
	"Can't create directory <%.500s...>: it would exceed MAXPATHLEN = %d\n",
	    prefix, MAXPATHLEN);
	}
	return NULL;
    }

    strcpy(file, prefix);

    if (!*hostname)
	return file;

#ifndef HAVE_LONG_FILE_NAMES
    strncpy(str, hostname, 14);
    str[14] = '\0';	/* in case exactly 14 chars were copied */
#else
    strcpy(str, hostname);
#endif
    for (s = strrchr(str, '.'); s; *s = '\0', s = strrchr(str, '.')) {
	strcat(file, "/");
	strcat(file, s+1);
    }
    strcat(file, "/");
    strcat(file, str);

    return file;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Creates a directory, including any needed directories leading
 * to it.  Returns -1 on stat/mkdir failure; 0 otherwise.
 * WARNING: doesn't check if final component is a directory.
 *
 * *err is returned with an errno value, if there was an error
 * for which there's a relevant errno.  Otherwise it's set to 0.
 *
 * If msgbuf is non-null pointer, then it's *assumed* long enough
 * for error message (1000 chars is enough), and error messages
 * are printed into it (without the following perror() value, which
 * should be added later; otherwise, error messages are Error()'d.
 */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
makedir(dir, err, msgbuf)
char *dir;	/* path with directories only. */
int *err;
char *msgbuf;
{
    static struct stat st;
    char *q;
    char path[MAXPATHLEN];

    *err = errno;

    /* First create directories along way, if necessary */
    strcpy(path, dir);

    for (q=path; q && *q; ) {

	/* skip leading slashes */
	while (*q == '/')
	    q++;

	/* check directory before next slash */
	q = strchr(q, '/');
	if (q)
	    *q = '\0';

	/* Stat directory; if missing, create it */
	if (stat(path, &st) != 0) {
	    if (errno != ENOENT) {
		*err = errno;
		if (msgbuf) {
		    sprintf(msgbuf,
			    	"Failed to stat directory <%.500s>: ", path);
		} else {
		    Error(1, 0, "Failed to stat directory <%.500s>: ", path);
		}
		return -1;
	    } else if (mkdir(path, 0700) != 0) {
		*err = errno;
		if (msgbuf) {
		    sprintf(msgbuf,
			    "Failed to create directory <%.500s>: ", path);
		} else {
		    Error(1, 0, "Failed to create directory <%.500s>: ", path);
		}
		return -1;
	    }
	}

	/* Restore slash */
	if (q)
	    *q = '/';
    }

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Adds a variable definition.
 * Returns 0 on success, -1 on error.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
add_variable(varname, vardefn)
char *varname;
char *vardefn;
{
    /*
     * Allocate space for varname, vardefn, and insert into
     * vars table.
     */
    ENTRY item, *found_item;
    char *keyspace, *dataspace;
    int ln = strlen(varname);
    int lb = strlen(vardefn);

    if (!created_variables_table) {
	if (!s_hcreate(HS_VARS, 1000))
	    return Error(0, 0,
		"$$Couldn't allocate hash table for variable processing\n");
	created_variables_table = 1;
    }

    if (debug > 1)
	fprintf(stderr, "entering add_variable(\"%s\",\"%s\")\n",
		varname, vardefn);

    keyspace = malloc(ln+1);
    dataspace = malloc(lb+1);
    if (!keyspace || !dataspace) {
	return Error(0, 0,
	    "$$Failed to allocate space for definition of variable `%s'\n",
	    varname);
    }

    item.key = keyspace;
    item.data = dataspace;
    strcpy(item.key, varname);
    strcpy(item.data, vardefn);

    if (debug > 1)
	fprintf(stderr, "Adding variable `%s' = `%s'\n", item.key, item.data);

    /* Delete the existing value, if any */
    if ((found_item = s_hsearch(HS_VARS, item, FIND))) {
	/* Assume all data allocated via malloc() */
	free(found_item->data);
	found_item->data = NULL;
    }
    if (!s_hsearch(HS_VARS, item, ENTER))
	return Error(0, 0,
	"$$Failed to allocate space for hash-table entry for variable `%s'\n",
		varname);

    return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Add sysinfo(2) items as variables.  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
add_sysinfo_variables()
{
    char buf[500];

    /* silly gimmick: define a variable using empty buf[] instead of "",
     * so that gcc -Wall doesn't complain if buf isn't used in at least
     * one of the ifdef'd regions, below.
     */
    buf[0] = '\0';
    add_variable("SI_ARCHITECTURE", buf);
#ifdef HAVE_SYSINFO
#ifdef SI_ARCHITECTURE
    if (sysinfo(SI_ARCHITECTURE, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_ARCHITECTURE", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_HOSTNAME", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_HOSTNAME
    if (sysinfo(SI_HOSTNAME, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_HOSTNAME", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_HW_PROVIDER", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_HW_PROVIDER
    if (sysinfo(SI_HW_PROVIDER, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_HW_PROVIDER", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_HW_SERIAL", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_HW_SERIAL
    if (sysinfo(SI_HW_SERIAL, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_HW_SERIAL", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_MACHINE", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_MACHINE
    if (sysinfo(SI_MACHINE, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_MACHINE", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_RELEASE", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_RELEASE
    if (sysinfo(SI_RELEASE, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_RELEASE", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_VERSION", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_VERSION
    if (sysinfo(SI_VERSION, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_VERSION", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_SRPC_DOMAIN", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_SRPC_DOMAIN
    if (sysinfo(SI_SRPC_DOMAIN, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_SRPC_DOMAIN", buf) == -1)
	    return -1;
    }
#endif
#endif

    if (add_variable("SI_SYSNAME", "") == -1)
	return -1;
#ifdef HAVE_SYSINFO
#ifdef SI_SYSNAME
    if (sysinfo(SI_SYSNAME, buf, sizeof(buf)) != -1) {
	if (add_variable("SI_SYSNAME", buf) == -1)
	    return -1;
    }
#endif
#endif
    return 0;
}

#ifdef HAVE_UNAME
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Add uname(2) items as variables.  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
add_uname_variables()
{
    struct utsname uts;

    if (uname(&uts) != -1) {
	if (add_variable("UNAME_SYSNAME", uts.sysname) ||
	    add_variable("UNAME_NODENAME", uts.nodename) ||
	    add_variable("UNAME_RELEASE", uts.release) ||
	    add_variable("UNAME_VERSION", uts.version) ||
	    add_variable("UNAME_MACHINE", uts.machine))
		return -1;
    } else {
	if (add_variable("UNAME_SYSNAME", "") ||
	    add_variable("UNAME_NODENAME", "") ||
	    add_variable("UNAME_RELEASE", "") ||
	    add_variable("UNAME_VERSION", "") ||
	    add_variable("UNAME_MACHINE", ""))
		return -1;
    }
    return 0;
}
#endif

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Does variable substitution on a string.  The result is stored in
 * an internal, reused buffer, and a pointer to that buffer is returned.
 * The caller must copy the returned string to a safe place before calling
 * any routine that might reuse the buffer.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
do_variables(string)
char *string;
{
    char c, *dollar, *varname;
    char *tail=NULL;
    char savechar;
    char key[2];
    int l;
    ENTRY wanted_item, *found_item;

    dollar = strchr(string, '$');	/* Special early return for no vars */
    if (!dollar)
	return string;

    if (debug > 1)
	fprintf(stderr, "Applying variable expansion to: `%s'\n", string);

    if (!created_variables_table) {
	Error(0, 0,
"$$No variables have been defined, but super.tab file is using some anyway!\n");
	return NULL;
    }

    variablebuf.l = 0;		/* Clear the buffer */

    /* Initialize the buffer to be at least the same size as the input buffer */
    if (!checksize(&variablebuf, ebuf.l)) {
	Error(1, 0, "$$Couldn't increase space for variable processing\n");
	return NULL;
    }

    while (string && *string) {
	/* First, copy up to variable character */
	dollar = strchr(string, '$');
	if (!dollar)
	    l = strlen(string);
	else
	    l = dollar - string;
	strncpy(variablebuf.buf + variablebuf.l, string, l);
	variablebuf.l += l;
	variablebuf.buf[variablebuf.l] = '\0';
	string += l;

	/* Reached end of string? */
	if (!*string)
	    break;

	/* Not at string end; expand variable */

	if ( (c = *(dollar+1)) == '$') {
	    /* $$ is a special macro name */
	    key[0] = c;
	    key[1] = '\0';
	    varname = key;
	    string += 2;
	    savechar = '\0';

	} else if (isalnum(c) || c == '_') {
	    /* Replace $name -> variable defn */
	    for (tail=dollar+2; isalnum(*tail) || *tail == '_'; tail++)
		;
	    varname = dollar+1;
	    /* tail points to one past last char in variable name.
	     * Save this character, then overwrite with null char.
	     */
	    savechar = *tail;
	    *tail = '\0';
	    string = tail;

	} else if (c == '(') {
	    /* Replace $(name) -> variable defn */
	    for (tail=dollar+2; isalnum(*tail) || *tail == '_'; tail++)
		;
	    /* must have reached right paren, and must make sure first
	     * character was alphabetic
	     */
	    if (tail == dollar+2) {
		Error(0, 0, "$$Empty variable name is illegal: `$()'\n");
		return NULL;

	    } else if (*tail != ')' || !isalpha(*(dollar+2))) {
		Error(0, 0, "$$Not a valid variable name: `%.*s'\n",
				(int) (tail+1-dollar), dollar);
		return NULL;
	    }
	    savechar = '\0';
	    *tail = '\0';
	    varname = dollar+2;
	    string = tail+1;

	} else {
	    /* All other $X is error */
	    Error(0, 0,
		"$$Illegal variable name `$%c'.  \n\
(Use `$$' to get a plain `$' passed on to the rest of file parsing.)\n", c);
	    return NULL;
	}

	if (debug > 1) {
	    fprintf(stderr, "Variable name is `%s'\n", varname);
	}
	if (varname) {
	    /* Have a variable requiring expansion */
	    wanted_item.key = varname;
	    found_item = s_hsearch(HS_VARS, wanted_item, FIND);
	    if (!found_item) {
		Error(0, 0, "$$\n\tNo such variable as `$%s'\n",
			wanted_item.key);
		return NULL;
	    }
	    l = strlen(found_item->data);
	    if (!checksize(&variablebuf, ROUNDUP(l+1, 1024))) {
	    Error(1, 0, "$$Couldn't increase space for variable processing\n");
		return NULL;
	    }
	    strcpy(variablebuf.buf+variablebuf.l, found_item->data);
	    variablebuf.l += l;
	    variablebuf.buf[variablebuf.l] = '\0';
	    /* Restore the saved character */
	    if (savechar && tail)
		*tail = savechar;
	}
    }

    if (debug > 1)
	fprintf(stderr, "variable-expanded string is `%s'\n", variablebuf.buf);

    return variablebuf.buf;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Returns a variable's value. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
get_variable(varname)
char *varname;
{
    ENTRY wanted_item, *found_item;

    /* Have a variable requiring expansion */
    wanted_item.key = varname;
    found_item = s_hsearch(HS_VARS, wanted_item, FIND);
    if (!found_item) {
	return NULL;
    }
    return found_item->data;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* A function for printing one variable's name and value.
 * See hprint() for calling sequence.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
print_variable(indx, key, data)
int indx;
char *key;
char *data;
{
    /* Ignore the variable "$", which is just a cute way of getting
     * "$$" to translate to plain "$".
     */
    if (strcmp(key, "$") != 0)
	printf("\t%s:\t%s\n", key, data);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Input is string like   "lhs<sep>rhs"
 * If there is no <sep>, null ptr is returned.
 * If lhs == left, then ptr to rhs is returned; else null pointer.
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
char *
str_val(left, sep, str)
char *left;
int sep;
char *str;
{
    char *s = strchr(str, sep);

    if (!s					/* equal sign? */
	|| strlen(left) != (s - str)		/* not same size as `left'? */
	|| strncmp(left, str, s-str) != 0)	/* lhs != left */
	return NULL;
    
    return s+1;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* re_comp()-style interface to wildmat. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static char *shell_pattern = NULL;
char *
shell_compile(s)
char *s;
{
    shell_pattern = s;
    return NULL;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* re_exec()-style interface to wildmat. */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
shell_compare(str)
char *str;
{
    extern int wildmat P__((char *str, char *pat));
    int l, Reverse;
    char *sp;

    /* If the pattern is [[...]] (that is, bounded by doubled
     * square brackets), then we have a special case:
     * every character in s must match the glob pattern [...].
     * For consistency with our normal shell patterns, we allow
     * ^[[...]] to invert the overall sense of match, but note
     * that the effect is the same as [[^...]].
     */
    Reverse = 0;
    sp = shell_pattern;
    if (*sp == '^') {
	Reverse = 1;
	sp++;
    }
    l = strlen(sp);
    if (sp[0] == '[' && sp[1] == '[' && sp[l-1] == ']' && sp[l-2] == ']') {
	/*
	 * it's the special case...
	 */
	int last, matched, reverse;
	char *p;
	
	for ( ; *str; str++) {
	    p = sp+1;			/* skip leading "[" of "[[" */
	    if (p[1] == '^') {
		reverse = !Reverse;	/* Inverted character class. */
		p++;
	    } else {
		reverse = Reverse;
	    }
	    matched = 0;
	    if (p[1] == ']' || p[1] == '-') {
		if (*++p == *str) {
		    matched = 1;
		}
	    }
	    for (last = *p; *++p && *p != ']'; last = *p) {
		if (*p == '-' && p[1] != ']'
		    ? *str <= *++p && *str >= last : *str == *p) {
		    matched = 1;
		}
	    }
	    if (matched == reverse) {
		return 0;		/* fail */
	    }
	}
	return 1;
    } else {
	return wildmat(str, shell_pattern);
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
init_strqtokS()
{
    unsigned char *p;
    memset(my_qm, '\0', sizeof(my_qm));
    for (p=(unsigned char *) QM; *p; )
	my_qm[*p++] = 1;

    memset(my_cc, '\0', sizeof(my_cc));
    for (p=(unsigned char *) CM; *p; )
	my_cc[*p++] = 1;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Finds the hostname, like gethostname(), then uses canonicalize_hostname
 * to change it to a canonical form.
 * Returns 0 on success, and -1 on error, like gethostname().
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
get_canonical_hostname(buf, len)
char *buf;
int len;
{
    if (gethostname(buf, len) < 0)
	return Error(0, 0, "gethostname(\"%s\", %d) failed\n", buf, len);
    
    return canonicalize_hostname(buf, len);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Canonicalizes a hostname, if available.
 * Returns 0 on success, and -1 on error, like gethostname().
 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int
canonicalize_hostname(buf, len)
char *buf;
int len;
{
#ifdef HAVE_GETHOSTBYNAME
    struct hostent *he, *he_nodot, *he_dot;
    char dot_name[1024] = "", nodot_name[1024] = "";


    if (globalinfo.gethostbyname) {
	if ((l=strlen(buf)) > len - 2 || l > sizeof(dot_name)-1)
	    return Error(0, 0,
			"hostname %s is too long for our buffer!\n", buf);
	/* On systems using DNS, appending a dot should force gethostbyname()
	 * to fully expand and return fqdn.  On systems using NIS, appending a
	 * dot will cause match failure.  So we try both forms, and take the
	 * longer match.
	 */
	he_nodot = gethostbyname(buf);
	if (he_nodot && he_nodot->h_name)
	    strcpy(nodot_name, he_nodot->h_name);
	strcat(buf, ".");
	he_dot = gethostbyname(buf);
	if (he_dot && he_dot->h_name)
	    strcpy(dot_name, he_dot->h_name);
	if (strlen(dot_name) && strlen(nodot_name)) {
	    if ((strlen(dot_name) > len - 1) || (strlen(nodot_name) > len - 1))
	        return Error(0, 0,
		"gethostbyname() returns `%s' which is too long for our buffer!\n",
		    buf);
	    strcpy(buf, strlen(dot_name) > strlen(nodot_name) ?
							dot_name : nodot_name);
	} else if (strlen(dot_name)) {
	    if (strlen(dot_name) > len - 1)
	        return Error(0, 0,
			"gethostbyname() returns `%s' which is too long for our buffer!\n",
			     buf);
	    strcpy(buf, dot_name);
	} else if (strlen(nodot_name)) {
	    if (strlen(nodot_name) > len - 1)
	      return Error(0, 0,
		"gethostbyname() returns `%s' which is too long for our buffer!\n",
			   buf);
	    strcpy(buf, nodot_name);
	} else {
	    return Error(0, 0,
		    "host `%s' not recognized by gethostbyname().\n", buf);
	}
	/* Delete trailing dot, if still present */
	if ((l=strlen(buf)) > 0 && buf[l-1] == '.')
	    buf[l-1] = '\0';
    }
#endif
    
    return 0;
}

/*
 * Check for balanced braces.
 * Return 0 if balanced, else (nleft-nright).
 */
int balancedbraces(s)
char *s;
{
    int n;
    for (n=0; *s; s++) {
	switch (*s) {
	case '{': n++; break;
	case '}': n--; break;
	}
    }
    return n;
}

/*
 * Downcase a null-terminated string.
 */
void
strtolower(s)
char *s;
{
    for (; *s; s++)
	if (isupper(*s))
	    *s = tolower(*s);
}


#ifndef HAVE_STRTOL
static unsigned long digit_a2d[128] = {0};

/* Initialize the character-to-digit mapping. */
static void
init_digit(void)
{
    int c;
    for (c=0; c < NELEM(digit_a2d); c++)
	digit_a2d[c] = -1;

    for (c = '0' ; c <= '9'; c++)
	digit_a2d[c] = c - '0';

    digit_a2d['a']=digit_a2d['A'] = 10; digit_a2d['b']=digit_a2d['B'] = 11;
    digit_a2d['c']=digit_a2d['C'] = 12; digit_a2d['d']=digit_a2d['D'] = 13;
    digit_a2d['e']=digit_a2d['E'] = 14; digit_a2d['f']=digit_a2d['F'] = 15;
    digit_a2d['g']=digit_a2d['G'] = 16; digit_a2d['h']=digit_a2d['H'] = 17;
    digit_a2d['i']=digit_a2d['I'] = 18; digit_a2d['j']=digit_a2d['J'] = 19;
    digit_a2d['k']=digit_a2d['K'] = 20; digit_a2d['l']=digit_a2d['L'] = 21;
    digit_a2d['m']=digit_a2d['M'] = 22; digit_a2d['n']=digit_a2d['N'] = 23;
    digit_a2d['o']=digit_a2d['O'] = 24; digit_a2d['p']=digit_a2d['P'] = 25;
    digit_a2d['q']=digit_a2d['Q'] = 26; digit_a2d['r']=digit_a2d['R'] = 27;
    digit_a2d['s']=digit_a2d['S'] = 28; digit_a2d['t']=digit_a2d['T'] = 29;
    digit_a2d['u']=digit_a2d['U'] = 30; digit_a2d['v']=digit_a2d['V'] = 31;
    digit_a2d['w']=digit_a2d['W'] = 32; digit_a2d['x']=digit_a2d['X'] = 33;
    digit_a2d['y']=digit_a2d['Y'] = 34; digit_a2d['z']=digit_a2d['Z'] = 35;
}

long
strtol(register const char *str, char **ptr, int base)
{
    long sign=1;
    long value=0;
    long digit;

    if (*digit_a2d == 0)
	init_digit();

    /* skip leading whitespace */
    while (isspace(*str))
        ++str;

    /* Check for optional sign */
    switch (*str) {
    case '-':
	sign = -1;
	str++;
	break;
    case '+':
	str++;
	break;
    }

    /* Determine base */
    if (base == 0) {
        if (*str != '0') {
            base = 10;
        } else if (*++str == 'x' || *str == 'X') {
            base = 16;
            ++str;
        } else {
            base = 8;
        }
    }

    /* Skip 0[xX], if present */
    if (base == 16 && *str == '0')
	if (*++str == 'x' || *str == 'X')
	    ++str;

    /* Convert value */
    while (*str < NELEM(digit_a2d) &&
		(digit=digit_a2d[*str]) < base && digit != -1) {
	value = value*base + digit;
	++str;
    }

    if (ptr)
	*ptr = (char *) str;
    
    return sign*value;
}
#endif

#ifndef HAVE_MEMSET
void *
memset(s, c, n)
void *s;
int c;
int n;
{
    register int i;
    register unsigned char *p = (unsigned char *) s;

    for (i=0; i<n; i++)
	*p++ = (unsigned char) c;
}
#endif

#ifdef HAVE_SYS_ERRLIST
    extern char *sys_errlist[];
    extern int sys_nerr;
#endif

/*
 * Safe strerror(): range-checks the argument.
 * (C standard doesn't guarantee any range checks.)
 */
char
*Strerror(errnum)
int errnum;
{
    static char buf[500];
#ifndef HAVE_SYS_ERRLIST
    sprintf(buf, "Error %d", errnum);
    return buf;
#else
    if (errnum < 0 || errnum > sys_nerr) {
	sprintf(buf, "Error %d (!)", errnum);
	return buf;
    } else {
#ifdef HAVE_STRERROR
	return strerror(errnum);
#else
	return sys_errlist[errnum];
#endif
    }
#endif
}

#ifndef HAVE_STRDUP
char *
strdup(s)
char *s;
{
    char *t = (char *) malloc(strlen(s)+1);
    if (!t) {
	return NULL;
    }
    strcpy(t, s);
    return t;
}
#endif

#ifndef HAVE_VPRINTF
/* Very system-specific.  May not work for you...
 */
void
vfprintf(stream, format, ap)
FILE *stream;
char *format;
va_list ap;
{
    _doprnt(format, ap, stream);
}

void
vsprintf(str, format, ap)
char *str;
char *format;
va_list ap;
{
    FILE fp;
    fp._cnt = 077777;
    fp._base = fp._ptr = str;
    fp._bufsiz = 0;
    fp._flag = _IOSTRG | _IOWRT;
    fp._file = 0;

    _doprnt(format, ap, &fp);
}
#ifndef HAVE_DOPRNT
ERROR -- YOU HAVE NEITHER v?printf() NOR _doprnt().
YOU ARE HOSED UNTIL YOU SUPPLY ONE OF THESE.
#endif
#endif

/***********************************************************************/

/*
 * my_alloc, my_realloc, my_free can be handy to add debugging info to,
 * when allocation errors are happening.
 */
void *
my_malloc(size_t size)
{
    void *p = malloc(size);
    printf("my_malloc(%lu) -> %p\n", (long unsigned int) size, p);
    return p;
}
void *
my_realloc(void *ptr, size_t size)
{
    void *p = realloc(ptr, size);
    printf("my_realloc(%p, %lu) -> %p\n", ptr, (long unsigned int) size, p);
    return p;
}
void
my_free(void *ptr)
{
    printf("my_free(%p)\n", ptr);
    free(ptr);
    return;
}
