|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T e
Length: 17996 (0x464c)
Types: TextFile
Names: »expand.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/expand.c«
/* @(#)expand.c 3.31 1/26/89 00:29:36 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* expand.c:
* expand filenames used by directors.
*
* external functions: expand_string, build_cmd_line
*/
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include "defs.h"
#include "smail.h"
#include "addr.h"
#include "transport.h"
#include "log.h"
#include "alloc.h"
#include "dys.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "debug.h"
# include "extern.h"
#endif
/* library functions */
extern long time();
/* functions local to this file */
static char **build_argv();
static char *substitute();
static char *lc_fold();
static char *uc_fold();
static char *strip_fold();
#ifndef NODEBUG
static void bad_subst();
#endif
\f
/*
* expand_string - expand a string containing parts to be expanded
*
* This function does ~user and ~/ expansion and also performs expansions
* of the form $name or ${name}. See substitute() for the possible
* substitutions.
*
* If `addr' is NULL, then a dummy addr structure is formed with all
* items NULL except for home and user, which are taken from the
* arguments to expand_string().
*
* return NULL on error attempting expansion. The area returned may
* be reused on subsequent calls. If the caller wishes to retain the
* returned data, it should be copied elsewhere.
*/
char *
expand_string(string, addr, home, user)
register char *string; /* unexpanded string */
struct addr *addr; /* addr structure with values */
char *home; /* home directory */
char *user; /* user name for $user */
{
char *save_string = string; /* save pointer to start of string */
static struct str str; /* build strings here */
static int inited = FALSE; /* TRUE if str inited */
int modified = FALSE; /* TRUE if any expansion ocured */
static struct addr *dummy_addr = NULL; /* dummy addr for home and user */
DEBUG3(DBG_DRIVER_HI, "expand_string(%s, %s, %s) called\n",
string, home, user);
if (! inited) {
STR_INIT(&str);
inited = TRUE;
} else {
STR_CHECK(&str);
str.i = 0;
}
if (addr == NULL) {
/* no addr structure given, setup a dummy one */
if (dummy_addr == NULL) {
dummy_addr = alloc_addr();
}
addr = dummy_addr;
addr->home = home;
addr->next_addr = user;
}
if (string[0] == '~') {
/* do some kind of twiddle expansion */
if (string[1] == '/') {
/* ~/ turns into home/ */
if (addr->home) {
modified = TRUE;
string++;
STR_CAT(&str, addr->home);
} else {
/* no home directory, so ~/ is not valid */
DEBUG(DBG_DRIVER_MID, "no home directory, ~/ invalid\n");
return NULL;
}
} else {
/* ~user turns into home director for the given user */
char *p = string + 1;
struct passwd *pw;
extern struct passwd *getpwbyname();
while (*string && *string != '/') {
string++;
}
if (*string) {
*string = '\0';
pw = getpwbyname(p);
*string = '/';
} else {
pw = getpwbyname(p);
}
if (pw == NULL) {
/* ~user but username isn't valid */
DEBUG1(DBG_DRIVER_MID, "user not found, ~%s invalid\n", p);
return NULL;
}
modified = TRUE;
STR_CAT(&str, pw->pw_dir);
}
}
/*
* we have the silly ~ business out of the way, now
* get all of the rest of the silly business out of the way
*/
while (*string) {
if (*string == '$') {
/* do a $-substitution */
string++;
if (*string == '{') {
/*
* handle expansions of the form ${name}
*/
char *p = string + 1;
char *new;
while (*string && *string != '}') {
string++;
}
if (*string == '\0') {
/* no matching } for the opening ${ */
return NULL;
}
new = substitute(addr, (char *)NULL, p, string - p);
if (new) {
STR_CAT(&str, new);
} else {
/* unrecognized substitution */
#ifndef NODEBUG
bad_subst(p, string - p);
#endif
return NULL;
}
string++;
} else {
/*
* handle $name expansions
*/
char *p = string;
char *new;
while (*string && (isalnum(*++string) || *string == '_')) ;
new = substitute(addr, (char *)NULL, p, string - p);
if (new) {
STR_CAT(&str, new);
} else {
/* unrecognized substitution */
#ifndef NODEBUG
bad_subst(p, string - p);
#endif
return NULL;
}
}
modified = TRUE;
} else {
/*
* regular character, copy it into the result
*/
STR_NEXT(&str, *string++);
}
}
if (!modified) {
/*
* no expansion needed to be done, just return the old string
*/
DEBUG1(DBG_DRIVER_HI, "unmodified, expand_string returns %s\n",
save_string);
return save_string;
}
/*
* expansion was done, finish up the string and return it
*/
STR_NEXT(&str, '\0');
DEBUG1(DBG_DRIVER_HI, "expand_string returns %s\n", str.p);
return str.p;
}
#ifndef NODEBUG
/*
* bad_subst - generate a debugging message for a failed substitution.
*
* note that we can't use "%*.*s" here since dprintf() is simple-minded.
*/
static void
bad_subst(var, len)
char *var;
int len;
{
int c_save = var[len];
var[len] = 0;
DEBUG1(DBG_DRIVER_MID, "expand_string: no expansion for $%s\n", var);
var[len] = c_save;
}
#endif
\f
/*
* build_cmd_line - build up an arg vector suitable for execv
*
* transports can call this to build up a command line in a standard
* way. Of course, if they want to they can build up a command line in
* a totally different fashion.
*
* Caution: return value points to a region which may be reused by
* subsequent calls to build_cmd_line()
*
* Notes on the replacement algorithm:
* o Within a $( and $) pair, substitutions are made once for
* each address on the input list.
* o Otherwise the substitution is made relative to the first
* address on the input list.
* o Substitutions:
* o grade ==> $grade
* o addr->next_host ==> $host
* o addr->next_addr ==> $addr or $user
* o addr->home ==> $home or $HOME
* o sender ==> $from or $sender
* o file ==> $file
* o message_id ==> $message_id
* o unix_date() ==> $ctime
* o get_arpa_date() ==> $date
* o getpid() ==> $$
* o uucp_name ==> $uucp_name
* o visible_name ==> $visible_name
* o primary_name ==> $primary_name
* o VERSION ==> $version
* o version() ==> $version_string
* o single quotes, double quotes and backslash work as with /bin/sh
*
* return NULL for parsing errors, and load `error' with a message
* explaining the error.
*/
char **
build_cmd_line(cmd, addr, file, error)
register char *cmd; /* input command line */
struct addr *addr; /* list of remote addresses */
char *file; /* substitution for $file */
char **error; /* error message */
{
static struct str str; /* generated region */
static int inited = FALSE; /* TRUE if str has been inited */
char *mark; /* temp mark in cmd line */
char *new; /* new string from substitute */
int ct = 1; /* count of args, at least one */
int state = 0; /* notes about parse state */
struct addr *save_addr = addr; /* replace addr from this after $) */
char *save_cmd; /* start of a $( ... $) group */
int last_char = '\0'; /* hold last *cmd value */
#define DQUOTE 0x01 /* double quote in effect */
#define GROUP 0x02 /* $( ... $) grouping in effect */
/* initialize for building up the arg vectors */
if (! inited) {
STR_INIT(&str);
inited = TRUE;
} else {
STR_CHECK(&str);
str.i = 0;
}
while (*cmd) {
switch (*cmd) {
case '\'':
/* after "'" copy literally to before next "'" char */
mark = index(cmd+1, '\'');
if (mark == NULL) {
panic(EX_DATAERR, "no matching ' for cmd in transport %s",
addr->transport->name);
/*NOTREACHED*/
}
*mark = '\0'; /* put null in for copy */
STR_CAT(&str, cmd+1);
*mark = '\''; /* put quote back */
last_char = '\'';
cmd = mark;
break;
case '\\':
/*
* char after \ is literal, unless in quote, in which case
* this is not so if the following char is not " or $ or \
*/
if (*cmd++ == '\0') {
*error = "\\ at end of command";
return NULL;
}
if (!(state&DQUOTE) ||
*cmd == '\\' || *cmd == '"' || *cmd == '$')
{
STR_NEXT(&str, *cmd);
} else {
STR_NEXT(&str, '\\');
STR_NEXT(&str, *cmd);
}
last_char = '\\';
break;
case '"': /* double quote is a toggle */
state ^= DQUOTE;
last_char = '"';
break;
case '$': /* perform parameter substitution */
cmd++;
if (*cmd == '\0') {
*error = "$ at end of command";
return NULL;
}
if (*cmd == '(') {
if (state&GROUP) {
*error = "recursive $( ... $)";
return NULL;
}
if (state&DQUOTE) {
*error = "$( illegal inside \"...\"";
return NULL;
}
save_cmd = cmd;
state |= GROUP;
break;
}
if (*cmd == ')') {
if ((state&GROUP) == 0) {
*error = "no match for $)";
return NULL;
}
if (state&DQUOTE) {
*error = "$) illegal inside \"...\"";
return NULL;
}
if (!isspace(last_char)) {
/* end previous vector, create a new one */
ct++;
STR_NEXT(&str, '\0');
}
addr = addr->succ;
if (addr) {
cmd = save_cmd;
} else {
/* no more addrs to put in group */
addr = save_addr;
state &= ~GROUP;
}
last_char = ' '; /* don't create an extra vector */
break;
}
if (*cmd == '{') {
mark = cmd+1;
cmd = index(mark, '}');
if (cmd == NULL) {
*error = "no match for {";
return NULL;
}
} else {
/* use at least one char after $ for substitute name */
mark = cmd;
while (isalnum(*++cmd) || *cmd == '_') ;
/* cmd now one beyond where it should be */
}
new = substitute(addr, file, mark, cmd - mark);
if (new == NULL) {
int c_save = mark[cmd-mark];
mark[cmd-mark] = '\0';
/* TODO: This is a memory leak */
*error = xprintf("bad substition: $s", mark);
mark[cmd-mark] = c_save;
return NULL;
}
STR_CAT(&str, new);
if (*cmd != '}') {
--cmd; /* correct next char pointer */
}
last_char = '$';
break;
case ' ': /* when not in a quote */
case '\t': /* white space separates words */
case '\n':
if (state&DQUOTE) {
STR_NEXT(&str, *cmd);
} else if (!isspace(last_char)) {
/* end the previous arg vector */
STR_NEXT(&str, '\0');
ct++; /* start a new one */
}
last_char = *cmd;
break;
default:
STR_NEXT(&str, *cmd);
last_char = *cmd;
}
cmd++; /* advance to next char */
}
if (state&DQUOTE) {
*error = "no match for opening \"";
return NULL;
}
if (state&GROUP) {
*error = "no match for $(";
return NULL;
}
if (isspace(last_char)) {
--ct; /* don't count just blanks */
}
STR_NEXT(&str, '\0'); /* null terminate the strings */
return build_argv(str.p, ct);
}
/*
* build_argv - build arg vectors from inline strings
*
* build_cmd_line produces chars with null characters separating
* strings. build_argv takes these chars and turns them into
* an arg vector suitable for execv.
*
* Caution: the value returned by build_argv() points to a region
* which may be reused on subsequent calls to build_argv().
*/
static char **
build_argv(p, ct)
register char *p; /* strings, one after another */
register int ct; /* count of strings */
{
static char **argv = NULL; /* reusable vector area */
static int argc;
register char **argp;
if (argv == NULL) {
argc = ct + 1;
argv = (char **)xmalloc(argc * sizeof(*argv));
} else {
if (ct + 1 > argc) {
X_CHECK(argv);
argc = ct + 1;
argv = (char **)xrealloc((char *)argv, argc * sizeof(*argv));
}
}
argp = argv;
DEBUG(DBG_REMOTE_MID, "cmd =");
while (ct--) {
*argp++ = p;
DEBUG1(DBG_REMOTE_MID, " '%s'", p);
if (ct) {
while (*p++) ; /* scan for next string */
}
}
DEBUG(DBG_REMOTE_MID, "\n");
*argp = NULL; /* terminate vectors */
return argv;
}
/*
* substitute - relace a $paramater with its value
*
* panic on errors, see build_cmd_line for details.
*/
static char *
substitute(addr, file, var, len)
struct addr *addr; /* source for $host, $addr, $user */
char *file; /* source for $file */
register char *var; /* start of variable */
register int len; /* length of variable */
{
#define MATCH(x) (len==sizeof(x)-1 && strncmpic(var, x, sizeof(x)-1) == 0)
if (strncmpic(var, "lc:", sizeof("lc:") - 1) == 0) {
return lc_fold(substitute(addr, file, var + 3, len - 3));
}
if (strncmpic(var, "uc:", sizeof("uc:") - 1) == 0) {
return uc_fold(substitute(addr, file, var + 3, len - 3));
}
if (strncmpic(var, "strip:", sizeof("strip:") - 1) == 0) {
return strip_fold(substitute(addr, file, var + 6, len - 6));
}
if (strncmpic(var, "parent:", sizeof("parent:") - 1) == 0) {
struct addr *parent = addr->parent;
if (parent == NULL) {
return NULL;
}
return substitute(parent, file, var + 7, len - 7);
}
if (strncmpic(var, "top:", sizeof("top:") - 1) == 0) {
struct addr *top = addr;
while (top->parent) {
top = top->parent;
}
return substitute(top, file, var + 4, len - 4);
}
if (MATCH("grade")) {
static char grade_str[2] = { 0, 0 };
grade_str[0] = msg_grade;
return grade_str;
}
if (MATCH("user") || MATCH("addr")) {
return addr->next_addr;
}
if (MATCH("host")) {
return addr->next_host;
}
if (MATCH("HOME") || MATCH("home")) {
return addr->home;
}
if (MATCH("sender") || MATCH("from")) {
return sender;
}
if (MATCH("file")) {
return file;
}
if (MATCH("message_id") || MATCH("id")) {
return message_id;
}
if (MATCH("ctime")) {
return unix_date();
}
if (MATCH("date")) {
/* get the current date in ARPA format */
return get_arpa_date(time((long *)0));
}
if (MATCH("spool_date")) {
/* get the spool date in ARPA format */
return get_arpa_date(message_date());
}
if (MATCH("$") || MATCH("pid")) {
static char pidbuf[10];
(void) sprintf(pidbuf, "%d", getpid());
return pidbuf;
}
if (MATCH("uucp_name")) {
return uucp_name;
}
if (MATCH("visible_name") || MATCH("name")) {
return visible_name;
}
if (MATCH("primary_name") || MATCH("primary")) {
return primary_name;
}
if (MATCH("version")) {
return version_number;
}
if (MATCH("version_string")) {
return version();
}
if (MATCH("release_date") || MATCH("release")) {
return release_date;
}
if (MATCH("patch_number") || MATCH("patch")) {
return patch_number;
}
if (MATCH("patch_date")) {
return patch_date;
}
if (MATCH("bat")) {
return bat;
}
if (MATCH("compile_num") || MATCH("ld_num")) {
static char s_compile_num[10];
(void) sprintf(s_compile_num, "%d", compile_num);
return s_compile_num;
}
if (MATCH("compile_date") || MATCH("ld_date")) {
return compile_date;
}
if (MATCH("smail_lib_dir") || MATCH("lib_dir")) {
return smail_lib_dir;
}
return NULL; /* no match */
#undef MATCH
}
/*
* lc_fold - meta substitution to convert value to lower case
*/
static char *
lc_fold(value)
register char *value;
{
static int lc_size; /* keep size of allocated region */
int value_size;
static char *lc = NULL; /* retained malloc region */
register char *p; /* for scanning through lc */
if (value == NULL) {
return NULL;
}
value_size = strlen(value) + 1;
/* get a region at least large enough for the value */
if (lc == NULL) {
lc = xmalloc(lc_size = value_size);
} else if (value_size > lc_size) {
X_CHECK(lc);
lc = xrealloc(lc, lc_size = value_size);
}
p = lc;
while (*value) {
*p++ = lowercase(*value++);
}
*p = '\0';
return lc;
}
/*
* uc_fold - meta substitution to convert value to upper case
*/
static char *
uc_fold(value)
register char *value;
{
static int uc_size; /* keep size of allocated region */
int value_size;
static char *uc = NULL; /* retained malloc region */
register char *p; /* for scanning through lc */
if (value == NULL) {
return NULL;
}
value_size = strlen(value) + 1;
/* get a region at least large enough for the value */
if (uc == NULL) {
uc = xmalloc(uc_size = value_size);
} else if (value_size > uc_size) {
X_CHECK(uc);
uc = xrealloc(uc, uc_size = value_size);
}
p = uc;
while (*value) {
*p++ = uppercase(*value++);
}
*p = '\0';
return uc;
}
/*
* strip_fold - strip quotes and collapse spaces and dots
*
* strip quotes from the input string and collapse any sequence of one
* or more white space and `.' characters into a single `.'.
*/
static char *
strip_fold(value)
char *value;
{
static int strip_size; /* keep size of allocated region */
int value_size;
static char *strip_buf = NULL; /* retained malloc region */
register char *p; /* for scanning through strip_buf */
register char *q; /* also for scanning strip_buf */
if (value == NULL) {
return NULL;
}
value_size = strlen(value) + 1;
if (strip_buf == NULL) {
strip_buf = xmalloc(strip_size = value_size);
} else if (value_size > strip_size) {
X_CHECK(strip_buf);
strip_buf = xrealloc(strip_buf, strip_size = value_size);
}
(void) strcpy(strip_buf, value);
(void) strip(strip_buf);
/* q reads and p writes */
p = q = strip_buf;
/* strip initial -'s */
while (*q == '-') {
q++;
}
while (*q) {
/* collapse multiple white-space chars and .'s into single dots */
if (isspace(*q) || *q == '.') {
while (isspace(*++q) || *q == '.') ;
*p++ = '.';
continue;
}
*p++ = *q++;
}
*p = '\0'; /* finish off strip_buf */
return strip_buf;
}