/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:expand.c,v 1.89 2005/10/28 04:34:52 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* 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 "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <grp.h>
#include <pwd.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "direct.h"
#include "route.h"
#include "lookup.h"
#include "log.h"
#include "smailstring.h"
#include "dys.h"
#include "spool.h"
#include "header.h"
#include "transport.h"
#include "smailconf.h"
#include "exitcodes.h"
#include "version.h"
#include "debug.h"
#include "extern.h"
#include "smailport.h"
/* functions local to this file */
static int expand_string_to __P((struct str *, char *, struct addr *, char *));
static int if_clause __P((struct addr *, char *, struct str *, char *, char *));
static int lookup_clause __P((struct addr *, struct str *, char *, char *));
#if 0
static int foraddrs_clause __P((struct addr *, struct str *, char *, char *));
#endif
static int foreach_clause __P((struct addr *, struct str *, char *, char *));
static int test_cond __P((struct addr *, char *, char **));
static char **build_argv __P((char *, int));
static char *substitute __P((struct addr *, char *, char *, char *, size_t));
static char *strip_making_filename __P((char *));
static char *uc_fold __P((char *));
static char *lc_fold __P((char *));
#ifdef notyet
static char *phrase_quote __P((char *));
#endif
static char *shell_quote __P((char *));
static char *regex_quote __P((char *));
static char *skip_ident __P((char *, char *));
static int skip_nesting __P((char **));
static int clause_token __P((char **, char **, size_t *));
static void bad_subst __P((char *, size_t, char *));
/*
* 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.
*
* XXX should have another pass-by-ref param to return reason for error.
*/
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 */
{
static struct str str; /* build strings here */
static int inited = FALSE; /* TRUE if str inited */
static struct addr *dummy_addr = NULL; /* dummy addr for home and user */
if (! inited) {
STR_INIT(&str);
inited = TRUE;
} else {
STR_CHECK(&str);
STR_CLEAR(&str);
}
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 0
DEBUG4(DBG_DRIVER_HI, "expand_string(%v, %v, %v, %v) called\n",
string,
addr->in_addr ? addr->in_addr : "(none)",
home ? home : "(nobody)",
user ? user : "(no-one)");
#endif
/*
* do the grunt work of expansion, appending to str
*/
if (expand_string_to(&str, string, addr, (char *) NULL) == FAIL) {
DEBUG(DBG_DRIVER_MID, "expand_string_to(): failed\n");
return NULL;
}
/*
* expansion was done, finish up the string and return it
*/
STR_NEXT(&str, '\0');
#if 0
DEBUG1(DBG_DRIVER_HI, "expand_string(): returns '%v'\n", STR(&str));
#endif
return STR(&str);
}
static int
expand_string_to(str, expr, addr, found)
struct str *str; /* output string */
char *expr; /* unexpanded expression */
struct addr *addr; /* addr structure with values */
char *found; /* value for $value */
{
char *string = expr;
DEBUG3(DBG_DRIVER_HI, "expand_string_to(str, %v, %v, %v) called\n",
string,
addr->in_addr ? addr->in_addr : "(none)",
found ? found : "(no-value)");
if (string[0] == '~') { /* do some kind of twiddle expansion */
if (string[1] == '/') { /* ~/ turns into home/ */
if (addr->home) {
string++;
STR_CAT(str, addr->home);
} else {
/* no home directory, so ~/ is not valid */
DEBUG(DBG_DRIVER_MID, "expanding ~/ failed: home directory unknown\n");
return FAIL;
}
} else { /* ~user turns into home directory for the given user */
char *p = string + 1;
char *u;
struct passwd *pw;
while (*string && *string != '/') {
string++;
}
u = strncpy(xmalloc((size_t) (string - p + 1)), p, (size_t) (string - p));
u[string - p] = '\0';
pw = getpwbyname(TRUE, p);
if (pw == NULL) {
/* ~user but username isn't valid */
DEBUG1(DBG_DRIVER_MID, "expanding '%s' failed: user not found\n", u);
xfree(u);
/* XXX should we just STR_CAT(str, u) anyway? */
return FAIL;
}
STR_CAT(str, pw->pw_dir);
xfree(u);
}
}
/*
* 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, *q, *new;
p = ++string;
if (skip_nesting(&string) == FAIL) {
/* error logged by skip_nesting() */
return FAIL;
}
--string; /* point to end of name, not '}' */
q = skip_ident(p, string);
if ((q - p) == 2 && strncmpic(p, "if", (size_t) 2) == 0) {
/*
* process conditional expansion
*/
if (if_clause(addr, found, str, q, string) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "'${%S' (if stmt) expands to nothing\n", string - p + 1, p);
}
} else if ((q - p) == 6 && strncmpic(p, "lookup", (size_t) 6) == 0) {
/*
* process database lookup
*/
if (lookup_clause(addr, str, q, string) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "'${%S' (lookup stmt) expands to nothing\n", string - p + 1, p);
}
#if 0
} else if ((q - p) == 7 && strncmpic(p, "foraddrs", (size_t) 7) == 0) {
/*
* process loop expansion for each addr in the list
*/
if (foraddrs_clause(addr, str, q, string) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "'${%S' (foraddrs stmt) expands to nothing\n", string - p + 1, p);
}
#endif
} else if ((q - p) == 7 && strncmpic(p, "foreach", (size_t) 7) == 0) {
/*
* for each entry in list expand value in the string
*/
if (foreach_clause(addr, str, q, string) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "'${%S' (foreach stmt) expands to nothing\n", string - p + 1, p);
}
} else {
/*
* It's not a conditional expression, or a database lookup,
* or a loop initiator, so it must be just a variable
* identifier...
*/
new = substitute(addr, (char *) NULL, found, p, (size_t) (string - p));
if (new) {
STR_CAT(str, new);
} else {
/* unrecognized substitution */
bad_subst("variable", (size_t) 0, xprintf("${%S} unknown, or not set", (size_t) (string - p), p));
return FAIL;
}
}
string++;
} else {
/*
* handle plain $name expansions as plain variable names
*/
char *p = string;
char *new;
string = skip_ident(string + 1, (char *) NULL);
new = substitute(addr, (char *) NULL, found, p, (size_t) (string - p));
if (new) {
STR_CAT(str, new);
} else {
/* unrecognized substitution */
bad_subst(p, (size_t) (string - p), "variable unknown, or not set");
return FAIL;
}
}
} else {
/*
* regular character, copy it into the result
*/
STR_NEXT(str, *string++);
}
}
DEBUG2(DBG_DRIVER_HI, "expand_string_to(): returns '%V'\n", STR_LEN(str), STR(str));
return SUCCEED;
}
/*
* if_clause - parse and perform ${if ...} expansion
*
* Two forms are possible:
*
* ${if condition [:]then-clause}
* ${if condition {then-clause} [else] [{else-clause}]}
*/
static int
if_clause(addr, found, str, s, es)
struct addr *addr;
char *found;
struct str *str;
char *s;
char *es;
{
int result;
char *then_clause, *then_end, *else_clause, *else_end;
char *q = s;
while (isspace((int) *q)) {
q++;
}
result = test_cond(addr, found, &q);
if (result == FAIL) {
return FAIL;
}
if (q >= es) {
DEBUG2(DBG_DRIVER_LO, "if_clause(): syntax error, in: '%Q'\n", (size_t) (es - s), s);
bad_subst(s, (size_t) 0, "syntax error in ${if condition");
return FAIL;
}
while (isspace((int) *q)) {
q++;
}
/* XXX overshooting 'es' should be impossible here... */
/*
* find then and else clauses of if.
*/
switch (*q) {
case '{':
then_clause = ++q;
if (skip_nesting(&q) == FAIL) {
return FAIL;
}
then_end = q - 1;
while (isspace((int) *q)) {
q++;
}
if (*q != '{' && strncmpic(q, "else", (size_t) 4) == 0) {
q += 4;
while (isspace((int) *q)) {
q++;
}
}
if (*q == '}') {
else_clause = es;
else_end = es;
break;
}
if (*q != '{') {
DEBUG2(DBG_DRIVER_LO, "if_clause(): syntax error, missing opening '{' after 'else' in: '%V'\n", (size_t) (es - s), s);
return FAIL; /* XXX should this be a syntax error? */
}
else_clause = ++q;
if (skip_nesting(&q) == FAIL) {
return FAIL;
}
else_end = q - 1;
while (isspace((int) *q)) {
q++;
}
if (q != es) {
DEBUG2(DBG_DRIVER_LO, "if_clause(): syntax error, over-stepped end of 'else' term in: '%V'\n", (size_t) (es - s), s);
return FAIL; /* XXX should this be a syntax error? */
}
break;
case ':':
q++;
/* FALLTHRU */
default:
then_clause = q;
then_end = es;
else_clause = es;
else_end = es;
break;
}
/*
* expand the then clause of the result matches
* the indicated test sense if: is a true sense,
* if! is a false sense; otherwise, expand the
* else clause.
*/
if (result) {
s = rcopy(then_clause, then_end);
} else {
s = rcopy(else_clause, else_end);
}
if (expand_string_to(str, s, addr, found) == FAIL) {
/* XXX DEBUG()? */
xfree(s);
return FAIL;
}
xfree(s);
return SUCCEED;
}
/*
* test_cond - test a conditional within an ${if: ...} or ${if! ...}
*
* Possible conditionals:
*
* !condition - reverse sense of test
* def:variable - true if variable is defined and non-empty
* def{meta:variable} - true if ${meta:variable} is expands non-empty
* header:string - true if indicated header exists in message
* origin:local - true if message origin is local
* origin:remote - true if message origin is remote
* dest:local - true if message is being delivered locally
* dest:remote - true if message is being delivered to remote host
* xform:local - transform into a local header
* xform:inet - transform into a RFC822-conformant envelope
* xform:uucp - transform into a UUCP-zone envelope
* xform:none - no specified transformation
* eq{var}{value} - test for match of variable with a value
* eqic{var}{value}- test for match of variable with a value, ignoring case
* gt{var}{value} - test for variable greater than value
* gtic{var}{value}- test for variable greater than value, ignoring case
* lt{var}{value} - test for variable less than value
* ltic{var}{value}- test for variable less than value, ignoring case
* or{{cond}...} - logical or of conditionals
* and{{cond}...} - logical and of conditionals
*
* TODO:
* rx{var}{RE} - test for match of variable with a regular expression
*/
static int
test_cond(addr, found, sp)
struct addr *addr;
char *found;
char **sp;
{
#define RESULT(res) ((negate) ? ((res) ? FALSE : TRUE) : ((res) ? TRUE : FALSE))
char *q = *sp;
char *s, *s2;
char *new;
size_t len, len2;
charplist_t *hdr;
unsigned long int tpflags;
struct str str;
int result;
int negate = FALSE;
int compare_op;
while (isspace((int) *q)) {
q++;
}
if (*q == '!') {
negate = TRUE;
q++;
}
*sp = q;
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find condition name at start of: '%v'\n", *sp);
return FAIL;
}
if (len == 3 && strncmpic(s, "def", (size_t) 3) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find variable for 'def' at start of: '%v'\n", *sp);
return FAIL;
}
new = substitute(addr, (char *) NULL, found, s, len);
return (new && *new) ? RESULT(TRUE) : RESULT(FALSE);
}
if (len == 6 && strncmpic(s, "header", (size_t) 6) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find name for 'header' at start of: '%v'\n", *sp);
return FAIL;
}
for (hdr = header; hdr; hdr = hdr->succ) {
if (strncmpic(s, hdr->text, len) == 0 &&
(hdr->text[len] == ':' || isspace((int) hdr->text[len])))
{
return RESULT(TRUE);
}
}
return RESULT(FALSE);
}
if (len == 6 && strncmpic(s, "origin", (size_t) 6) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find word for 'origin' at start of: '%v'\n", *sp);
return FAIL;
}
if (len == 5 && strncmpic(s, "local", (size_t) 5) == 0) {
return RESULT(islocal);
}
if (len == 6 && strncmpic(s, "remote", (size_t) 6) == 0) {
return RESULT(! islocal);
}
return FAIL;
}
if (len == 4 && strncmpic(s, "dest", (size_t) 4) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find word for 'dest' at start of: '%v'\n", *sp);
return FAIL;
}
if (addr == NULL || addr->transport == NULL) {
return RESULT(FALSE);
}
tpflags = addr->transport->flags;
if (len == 5 && strncmpic(s, "local", (size_t) 5) == 0) {
return RESULT(tpflags & LOCAL_TPORT);
}
if (len == 6 && strncmpic(s, "remote", (size_t) 6) == 0) {
return RESULT(! (tpflags & LOCAL_TPORT));
}
return FAIL;
}
if (len == 5 && strncmpic(s, "xform", (size_t) 5) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find word for 'xform' at start of: '%v'\n", *sp);
return FAIL;
}
if (addr == NULL || addr->transport == NULL) {
return FALSE;
}
tpflags = addr->transport->flags;
if (len == 5 && strncmpic(s, "local", (size_t) 5) == 0) {
return RESULT(tpflags & LOCAL_XFORM);
}
if (len == 6 && strncmpic(s, "uucp", (size_t) 6) == 0) {
return RESULT(tpflags & UUCP_XFORM);
}
if (len == 6 && strncmpic(s, "inet", (size_t) 6) == 0) {
return RESULT(tpflags & INET_XFORM);
}
if (len == 6 && strncmpic(s, "none", (size_t) 6) == 0) {
return RESULT(! (tpflags & (LOCAL_XFORM|UUCP_XFORM|INET_XFORM)));
}
return FAIL;
}
compare_op = 0;
if ((len == 2 || len == 4) &&
(((strncmpic(s, "eq", (size_t) 2) == 0) && (compare_op = 1)) ||
((strncmpic(s, "eqic", (size_t) 4) == 0) && (compare_op = 2)) ||
((strncmpic(s, "gt", (size_t) 2) == 0) && (compare_op = 3)) ||
((strncmpic(s, "gtic", (size_t) 4) == 0) && (compare_op = 4)) ||
((strncmpic(s, "lt", (size_t) 2) == 0) && (compare_op = 5)) ||
((strncmpic(s, "ltic", (size_t) 4) == 0) && (compare_op = 6)))) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "test_cond(): failed to find variable for '%s' at start of: '%v'\n",
(compare_op == 1) ? "eq" :
(compare_op == 2) ? "eqic" :
(compare_op == 3) ? "gt" :
(compare_op == 4) ? "gtic" :
(compare_op == 5) ? "lt" :
(compare_op == 6) ? "ltic" : "UNKNOWN-COMPARE",
*sp);
return FAIL;
}
if (clause_token(sp, &s2, &len2) == FAIL) {
DEBUG2(DBG_DRIVER_MID, "test_cond(): failed to value for '%s' at start of: '%v'\n",
(compare_op == 1) ? "eq" :
(compare_op == 2) ? "eqic" :
(compare_op == 3) ? "gt" :
(compare_op == 4) ? "gtic" :
(compare_op == 5) ? "lt" :
(compare_op == 6) ? "ltic" : "UNKNOWN-COMPARE",
*sp);
return FAIL;
}
s2 = rcopy(s2, s2 + len2);
STR_INIT(&str);
if (expand_string_to(&str, s2, addr, found) == FAIL) {
result = FAIL;
} else {
s = substitute(addr, (char *) NULL, found, s, len);
if (!s) {
s = "";
}
STR_NEXT(&str, '\0');
DEBUG3(DBG_DRIVER_HI, "test_cond(): about to do %s{'%q', '%q'} comparison\n",
(compare_op == 1) ? "eq" :
(compare_op == 2) ? "eqic" :
(compare_op == 3) ? "gt" :
(compare_op == 4) ? "gtic" :
(compare_op == 5) ? "lt" :
(compare_op == 6) ? "ltic" : "UNKNOWN-COMPARE",
s, STR(&str));
switch (compare_op) {
case 1:
result = (strcmp(s, STR(&str)) == 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
case 2:
result = (strcmpic(s, STR(&str)) == 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
case 3:
result = (strcmp(s, STR(&str)) > 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
case 4:
result = (strcmpic(s, STR(&str)) > 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
case 5:
result = (strcmp(s, STR(&str)) < 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
case 6:
result = (strcmpic(s, STR(&str)) < 0) ? RESULT(TRUE) : RESULT(FALSE);
break;
default:
result = FAIL; /* for lint & GCC -Wunused */
panic(EX_SOFTWARE, "test_cond(): unknown compare operation: '%v'", *sp);
/* NOTREACHED */
}
}
STR_FREE(&str);
xfree(s2);
return result;
}
if (len == 3 && strncmpic(s, "and", (size_t) 3) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find expression for 'and' at start of: '%v'\n", *sp);
return FAIL;
}
s = rcopy(s, s + len);
q = s;
result = TRUE;
for (;;) {
while (isspace((int) *q)) {
q++;
}
if (*q == '\0') {
break;
}
if (clause_token(&q, &s2, &len2) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find another expression for 'and' at start of: '%v'\n", *sp);
result = FAIL;
break;
}
result = test_cond(addr, found, &s2);
if (result != TRUE) {
break;
}
}
xfree(s);
return result == FAIL ? FAIL : RESULT(result);
}
if (len == 2 && strncmpic(s, "or", (size_t) 2) == 0) {
if (clause_token(sp, &s, &len) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find expression for 'or' at start of: '%v'\n", *sp);
return FAIL;
}
s = rcopy(s, s + len);
q = s;
result = FALSE;
for (;;) {
while (isspace((int) *q)) {
q++;
}
if (*q == '\0') {
break;
}
if (clause_token(&q, &s2, &len2) == FAIL) {
DEBUG1(DBG_DRIVER_MID, "test_cond(): failed to find another expression for 'or' at start of: '%v'\n", *sp);
result = FAIL;
break;
}
result = test_cond(addr, found, &s2);
if (result != FALSE) {
break;
}
}
xfree(s);
return result == FAIL ? FAIL : RESULT(result);
}
write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY, "test_cond(): unknown condition at start of: '%v'\n", s);
return FAIL;
#undef RESULT
}
/*
* lookup_clause - expand a lookup expansion
*
* Form:
* ${lookup:key:proto{file-expansion}
* [then] {then-clause}
* [else] {else-clause}}
* or:
* ${lookup:key:proto:file-expansion:then-clause}
*/
static int
lookup_clause(addr, str, s, es)
struct addr *addr;
struct str *str;
char *s;
char *es;
{
char *q = s;
char *key, *proto, *file;
char *xkey = NULL;
char *xproto = NULL;
char *xfile = NULL;
size_t key_len;
size_t proto_len, file_len;
char *then_clause, *then_end, *else_clause, *else_end;
struct str file_str;
char *dbinfo;
char *error = NULL;
char *found = NULL;
int dbresult;
int result;
/*
* get the key, proto, and database
*/
if (clause_token(&q, &key, &key_len) == FAIL ||
clause_token(&q, &proto, &proto_len) == FAIL ||
clause_token(&q, &file, &file_len) == FAIL)
{
DEBUG1(DBG_CONF_LO, "lookup_clause(): failed to find key & proto & file in '%v'\n", s);
return FAIL;
}
/*
* locate then and else clauses
*/
while (isspace((int) *q)) {
q++;
}
/* XXX overshooting 'es' should be impossible here... */
if (strncmpic(q, "then", (size_t) 4) == 0) {
q += 4;
while (isspace((int) *q)) {
q++;
}
}
switch (*q) {
case '{':
then_clause = ++q;
if (skip_nesting(&q) == FAIL) {
return FAIL;
}
then_end = q - 1;
while (isspace((int) *q)) {
q++;
}
if (*q != '{' && strncmpic(q, "else", (size_t) 4) == 0) {
q += 4;
while (isspace((int) *q)) {
q++;
}
}
if (*q == '}') {
else_clause = es;
else_end = es;
break;
}
if (*q != '{') {
DEBUG2(DBG_DRIVER_LO, "lookup_clause(): syntax error, missing opening '{' after 'else' in: '%V'\n", (size_t) (es - s), s);
return FAIL; /* XXX should this be a syntax error? */
}
else_clause = ++q;
if (skip_nesting(&q) == FAIL) {
return FAIL;
}
else_end = q - 1;
while (isspace((int) *q)) {
q++;
}
if (q != es) {
DEBUG2(DBG_DRIVER_LO, "lookup_clause(): syntax error, over-stepped end of 'else' term in: '%V'\n", (size_t) (es - s), s);
return FAIL; /* XXX should this be a syntax error */
}
break;
case ':':
q++;
/* FALLTHRU */
default:
then_clause = q;
then_end = es;
else_clause = es;
else_end = es;
break;
}
/*
* expand the key, file, and proto
*/
STR_INIT(&file_str);
xkey = substitute(addr, (char *) NULL, (char *) NULL, key, key_len);
if (xkey == NULL) {
goto do_else_clause;
}
xkey = COPY_STRING(xkey);
xfile = rcopy(file, file + file_len);
file = NULL;
if (expand_string_to(&file_str, xfile, addr, (char *) NULL) == FAIL) {
xfree(xkey);
xfree(xfile);
STR_FREE(&file_str);
return FAIL;
}
STR_NEXT(&file_str, '\0');
xproto = rcopy(proto, proto + proto_len);
proto = NULL;
/*
* open the database and do a lookup with the key
*/
dbresult = open_database(STR(&file_str), xproto, 1, 2, (struct stat *) NULL,
&dbinfo, &error);
switch (dbresult) {
case FILE_FAIL:
DEBUG3(DBG_DRIVER_LO, "Warning: open_database(%v, %v) failed: %s\n",
STR(&file_str), xproto, error);
goto do_else_clause;
case FILE_AGAIN:
case FILE_NOMATCH:
goto do_else_clause;
}
dbresult = lookup_database(dbinfo, xkey, &found, &error);
close_database(dbinfo);
switch (dbresult) {
case FILE_FAIL:
case DB_FAIL:
DEBUG4(DBG_DRIVER_LO, "Warning: lookup of %v in %v:%v failed: %s\n",
xkey, xproto, STR(&file_str), error);
goto do_else_clause;
case FILE_AGAIN:
case FILE_NOMATCH:
case DB_AGAIN:
case DB_NOMATCH:
goto do_else_clause;
}
/*
* remove white-space from beginning and end of found string
*/
while (isspace((int) *found)) {
found++;
}
for (q = found; *q; q++) {
if (*q == '#') {
break;
}
}
while (q > found && isspace((int) *(q - 1))) {
--q;
}
*q = '\0';
/*
* expand the then-clause
*/
s = rcopy(then_clause, then_end);
goto do_expand;
do_else_clause:
s = rcopy(else_clause, else_end);
/* FALLTHRU */
do_expand:
result = expand_string_to(str, s, addr, found);
if (xkey) {
xfree(xkey);
}
if (xproto) {
xfree(xproto);
}
if (xfile) {
xfree(xfile);
}
if (s) {
xfree(s);
}
STR_FREE(&file_str);
return result;
}
#if 0
/*
* foraddrs_clause - expand a list iterator for each addr in addrlst list
*
* Form:
* ${foraddrs:var:separator:pattern with ${value} expanded for var from each addr}
*
* e.g.:
* ${foraddrs:input_addr:,:<${quote:value}>}
*/
static int
foraddrs_clause(addrlst, str, s, es)
struct addr *addrlst;
struct str *str;
char *s;
char *es;
{
struct cur addr;
/* first parse out the var, separator, and pattern pointers */
/* lastly create the result */
for (cur = addrlst; cur; cur = cur->succ) {
}
}
#endif
/*
* foreach_clause - expand a list into list format
*
* Form:
* ${foreach:list:pattern with ${value} expanded to each item from $list}
*
* e.g.:
* ${foreach:more_hostnames:${rxquote:value};a common comment about ${value}}
*/
static int
foreach_clause(addr, str, s, es)
struct addr *addr;
struct str *str;
char *s;
char *es;
{
char *q = s;
char *listname = NULL;
size_t listname_len;
char *list;
char *pat;
char *pat_end;
char *ss;
char *item;
/*
* get the list name
*/
if (clause_token(&q, &listname, &listname_len) == FAIL) {
DEBUG2(DBG_CONF_LO, "foreach_clause(): failed to find list name in: '%V'\n", (size_t) (es - s), s);
return FAIL;
}
q++; /* skip the colon */
/*
* get the string pattern
*/
while (isspace((int) *q)) {
q++;
}
/* XXX overshooting 'es' should be impossible here... */
switch (*q) {
case '{':
pat = ++q;
if (skip_nesting(&q) == FAIL) {
return FAIL;
}
pat_end = q - 1;
while (isspace((int) *q)) {
q++;
}
if (q != es) {
DEBUG2(DBG_CONF_LO, "foreach_clause(): junk after closing '}' in: '%V'\n", (size_t) (es - s), s);
return FAIL; /* XXX syntax error */
}
break;
default:
pat = q;
pat_end = es;
break;
}
if (pat == pat_end) {
DEBUG(DBG_CONF_LO, "foreach_clause(): empty string pattern\n");
return FAIL; /* emptiness */
}
/* XXX should check for (unescaped?) colons in pat... */
/*
* expand the list to see if there's anything in it to work on
*/
list = substitute(addr, (char *) NULL, (char *) NULL, listname, listname_len);
if (!list) {
DEBUG2(DBG_CONF_LO, "foreach_clause(): substitute(addr, NULL, NULL, %S) failed\n", listname_len, listname);
return FAIL;
}
list = COPY_STRING(list);
ss = rcopy(pat, pat_end);
DEBUG3(DBG_CONF_HI, "foreach_clause(): about to iterate over %S with '%v'\n", listname_len, listname, ss);
/*
* for each entry in the list, expand the string segment and append the
* result to the output list
*/
for (item = strcolon(list); item; item = strcolon((char *) NULL)) {
if (!*item) {
continue; /* avoid expanding empty items */
}
/*
* 'q' is guaranteed not to be NULL initially so we can use it as a
* flag to avoid creating an empty item at the first expansion
*/
if (!q) {
STR_NEXT(str, ':');
} else {
q = NULL;
}
if (expand_string_to(str, ss, addr, item) == FAIL) {
DEBUG3(DBG_CONF_LO, "foreach_clause(): expand_string_to(str, %S, '%v') failed\n", listname_len, listname, item);
return FAIL;
}
}
if (list) {
xfree(list);
}
if (ss) {
xfree(ss);
}
return SUCCEED;
}
/*
* 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 as per expand_string() [i.e. smail(5)], and
* results are implicitly within one argument.
* o single quotes, double quotes and backslash work as with /bin/sh
* o The "vector" is initially stored in a struct str with each item
* separated by a NUL byte, and then build_argv() is called to break
* it into a proper char *argv[];
*
* return NULL for parsing errors, and load `error' with a message
* explaining the error.
*/
char **
build_cmd_line(cmd, addr, file, error)
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 *var; /* pointer to rcopy()ed $-expr from cmd */
size_t olen;
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 = NULL; /* 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 */
DEBUG2(DBG_DRIVER_MID, "build_cmd_line(cmd='%v', file='%v') called\n", cmd, file);
/* initialize for building up the arg vectors */
if (! inited) {
STR_INIT(&str);
inited = TRUE;
} else {
STR_CHECK(&str);
STR_CLEAR(&str);
}
while (*cmd) {
switch (*cmd) {
case '\'':
/* after "'" copy literally to before next "'" char */
/* XXX FIXME!!! what about quoted "'"? -- need skip_qstring(str, qchar) */
mark = strchr(cmd + 1, '\'');
if (mark == NULL) {
*error = xprintf("no matching ' for cmd in transport %v",
addr->transport->name);
return NULL;
}
STR_NCAT(&str, (cmd + 1), (size_t) (mark - cmd - 1));
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((int) last_char)) {
/* end previous vector, create a new one */
ct++;
STR_NEXT(&str, '\0');
}
/* is there another address in the list? */
addr = addr->succ;
if (addr) {
/* yes, reset parse pointer to beginning of group subexpression */
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++;
if (skip_nesting(&cmd) == FAIL) {
*error = xprintf("no match for '{': %v", mark);
return NULL;
}
} else {
mark = cmd - 1;
cmd = skip_ident(cmd + 1, cmd + strlen(cmd));
}
olen = STR_LEN(&str); /* remember expanded len up to now */
/*
* we have to copy out the $-expr substring from cmd because the
* latter may not be in writable storage...
*/
var = rcopy(mark, cmd); /* XXX use a static struct str ??? */
if (expand_string_to(&str, var, addr, (char *) NULL) == FAIL) {
/* XXX FIXME: This is a memory leak */
*error = xprintf("bad substition: %v", mark);
xfree(var);
return NULL;
}
xfree(var);
if (STR_LEN(&str) == olen) {
last_char = ' '; /* expanded to nothing -- don't make a vector! */
} else {
last_char = '$';
}
--cmd; /* correct the next char pointer */
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((int) 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((int) last_char)) {
--ct; /* don't count just blanks */
}
STR_NEXT(&str, '\0'); /* null terminate the strings */
DEBUG1(DBG_DRIVER_MID, "build_cmd_line() string count=%d.\n", ct);
return build_argv(STR(&str), 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, NUL-separated */
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) {
argc = ct + 1;
argv = (char **) xrealloc((char *) argv, argc * sizeof(*argv));
}
}
argp = argv;
DEBUG(DBG_DRIVER_MID, "cmd =");
while (ct--) {
*argp++ = p;
DEBUG1(DBG_DRIVER_MID, " '%v'", p);
if (ct) {
while (*p++) {
; /* scan for next string */
}
}
}
DEBUG(DBG_DRIVER_MID, "\n");
*argp = NULL; /* terminate vectors */
return argv;
}
/*
* substitute - relace a $paramater with its value
*
* may return NULL if substitution fails or has no value
*
* XXX returns pointers to static storage or storage held in some other
* structure such as configuration values or address related values.
*/
static char *
substitute(addr, file, found, var, len)
struct addr *addr; /* source for $host, $addr, $user */
char *file; /* source for $file */
char *found; /* $value value */
register char *var; /* start of variable */
register size_t len; /* length of variable */
{
static char buf[MAXLONG_B10_DIGITS + 1];
struct attr_table *conf_attr;
char *varname;
char *temp_str = NULL;
char *result;
#define SLEN(x) (sizeof(x) - 1)
DEBUG6(DBG_DRIVER_HI, "substitute('%v', '%v', '%v', '%S', %u) called\n",
addr->in_addr ? addr->in_addr : "(no-addr)",
file ? file : "(no-file)",
found ? found : "(no-$value)",
var ? len : SLEN("(no-var)"),
var ? var : "(no-var)",
len);
if (!var || len == 0) {
return NULL;
}
#define PREFMATCH(v, l, x) (l > SLEN(x) && strncmpic(v, x, SLEN(x)) == 0)
#define MATCH(v, l, x) (l == SLEN(x) && strncmpic(v, x, SLEN(x)) == 0)
if (PREFMATCH(var, len, "lc:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("lc:"), len - SLEN("lc:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = lc_fold(temp_str);
xfree(temp_str);
return result;
}
if (PREFMATCH(var, len, "uc:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("uc:"), len - SLEN("uc:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = uc_fold(temp_str);
xfree(temp_str);
return result;
}
if (PREFMATCH(var, len, "quote:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("quote:"), len - SLEN("quote:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = quote(temp_str, FALSE, FALSE);
xfree(temp_str);
return result;
}
#ifdef notyet
if (PREFMATCH(var, len, "phquote:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("phquote:"), len - SLEN("phquote:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = phrase_quote(temp_str);
xfree(temp_str);
return result;
}
#endif
if (PREFMATCH(var, len, "shquote:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("shquote:"), len - SLEN("shquote:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = shell_quote(temp_str);
xfree(temp_str);
return result;
}
if (PREFMATCH(var, len, "rxquote:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("rxquote:"), len - SLEN("rxquote:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = regex_quote(temp_str);
xfree(temp_str);
return result;
}
if (PREFMATCH(var, len, "strip:")) {
if (!(temp_str = substitute(addr, file, found, var + SLEN("strip:"), len - SLEN("strip:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
result = strip_making_filename(temp_str);
xfree(temp_str);
return result;
}
if (PREFMATCH(var, len, "eval:")) {
static struct str str; /* build strings here */
if (!addr) { /* expand string would fake it, but we won't... */
return NULL;
}
if (!(temp_str = substitute(addr, file, found, var + SLEN("eval:"), len - SLEN("eval:")))) {
return NULL;
}
temp_str = COPY_STRING(temp_str);
DEBUG3(DBG_DRIVER_HI, "substitute(): about to re-evalutate value of %S: '%v'\n",
len - SLEN("eval:"), var + SLEN("eval:"), temp_str);
STR_INIT(&str);
if (expand_string_to(&str, temp_str, addr, (char *) NULL) == FAIL) {
return NULL;
}
STR_NEXT(&str, '\0');
#if 0
xfree(temp_str); /* may be included in expand_string_to() value */
#endif
return STR(&str);
}
if (PREFMATCH(var, len, "parent:")) {
if (!addr || !addr->parent) {
return NULL;
}
return substitute(addr->parent, file, found, var + SLEN("parent:"), len - SLEN("parent:"));
}
if (PREFMATCH(var, len, "true_addr:")) {
if (!addr || !addr->true_addr) {
return NULL;
}
return substitute(addr->true_addr, file, found, var + SLEN("true_addr:"), len - SLEN("true_addr:"));
}
if (PREFMATCH(var, len, "top:")) {
struct addr *top = addr;
if (!top) {
return NULL;
}
while (top->parent) {
top = top->parent;
}
return substitute(top, file, found, var + SLEN("top:"), len - SLEN("top:"));
}
if (MATCH(var, len, "value")) {
return found;
}
if (MATCH(var, len, "grade")) {
static char grade_str[2] = { 0, 0 };
grade_str[0] = msg_grade;
return grade_str;
}
if (MATCH(var, len, "user") || MATCH(var, len, "addr")) {
return addr ? addr->next_addr : NULL;
}
if (MATCH(var, len, "host")) {
return addr ? addr->next_host : NULL;
}
if (MATCH(var, len, "route")) {
return addr ? addr->route : NULL;
}
if (MATCH(var, len, "input_addr")) {
return addr ? addr->in_addr : NULL;
}
if (MATCH(var, len, "mailbox")) {
return addr ? addr->remainder : NULL;
}
if (MATCH(var, len, "user_prefix")) {
return addr ? addr->rem_prefix : NULL;
}
if (MATCH(var, len, "user_suffix")) {
return addr ? addr->rem_suffix : NULL;
}
if (MATCH(var, len, "target_domain")) {
return addr ? (addr->target ? addr->target :
(addr->local_name ? addr->local_name :
(addr->parent->local_name ? addr->parent->local_name :
primary_name))) :
NULL;
}
if (MATCH(var, len, "target")) {
return addr ? addr->target : NULL;
}
if (MATCH(var, len, "local_domain")) {
return addr ? addr->local_name : NULL;
}
if (MATCH(var, len, "HOME") || MATCH(var, len, "home")) {
return addr ? addr->home : NULL;
}
if (MATCH(var, len, "owner")) {
return addr ? (addr->owner) : NULL;
}
if (MATCH(var, len, "return_address")) {
char *return_address;
return_address = get_return_addr(addr);
return return_address ? return_address : "MAILER-DAEMON";
}
if (MATCH(var, len, "sender") || MATCH(var, len, "from")) {
return sender;
}
if (MATCH(var, len, "sender_name") || MATCH(var, len, "fullname")) {
if (sender_name == NULL && islocal) {
getfullname();
}
return sender_name;
}
if (MATCH(var, len, "local_sender")) {
return local_sender;
}
if (MATCH(var, len, "file")) {
return file;
}
if (MATCH(var, len, "message_id") || MATCH(var, len, "id")) {
return message_id;
}
if (MATCH(var, len, "message_size") || MATCH(var, len, "size")) {
(void) sprintf(buf, "%lu", (unsigned long int) msg_size);
return buf;
}
if (MATCH(var, len, "message_body_size") || MATCH(var, len, "body_size")) {
(void) sprintf(buf, "%lu", msg_body_size);
return buf;
}
if (MATCH(var, len, "ctime")) {
return unix_date();
}
if (MATCH(var, len, "date")) {
/* get the current date in ARPA format */
return get_arpa_date(time((time_t *) NULL));
}
if (MATCH(var, len, "spool_date")) {
/* get the spool date in ARPA format */
return get_arpa_date(message_date());
}
if (MATCH(var, len, "$") || MATCH(var, len, "pid")) {
(void) sprintf(buf, "%ld", (long) getpid());
return buf;
}
if (MATCH(var, len, "uucp_name")) {
return uucp_name;
}
if (MATCH(var, len, "visible_name") || MATCH(var, len, "name")) {
return visible_name;
}
if (MATCH(var, len, "primary_name") || MATCH(var, len, "primary")) {
return primary_name;
}
if (MATCH(var, len, "version")) {
return version_number;
}
if (MATCH(var, len, "version_string")) {
return version();
}
if (MATCH(var, len, "release_date") || MATCH(var, len, "release")) {
return release_date;
}
if (MATCH(var, len, "compile_num") || MATCH(var, len, "ld_num")) {
static char s_compile_num[MAXINT_B10_DIGITS + 1];
(void) sprintf(s_compile_num, "%d", compile_num);
return s_compile_num;
}
if (MATCH(var, len, "compile_date") || MATCH(var, len, "ld_date")) {
return compile_date;
}
if (MATCH(var, len, "smail_lib_dir") || MATCH(var, len, "lib_dir")) {
return smail_lib_dir;
}
if (MATCH(var, len, "sender_host")) {
return sender_host;
}
if (MATCH(var, len, "sender_host_invalid")) {
return sender_host_invalid;
}
if (MATCH(var, len, "sender_host_addr")) {
return sender_host_addr;
}
if (MATCH(var, len, "sender_host_port")) {
return sender_host_port;
}
if (MATCH(var, len, "sender_host_really")) {
return sender_host_really;
}
if (MATCH(var, len, "sender_host_really_invalid")) {
return sender_host_really_invalid;
}
if (MATCH(var, len, "smtp_local_addr")) {
return smtp_local_addr;
}
if (MATCH(var, len, "smtp_local_port")) {
return smtp_local_port;
}
if (MATCH(var, len, "sender_proto")) {
return sender_proto;
}
if (MATCH(var, len, "ident_sender")) {
return ident_sender;
}
if (MATCH(var, len, "ident_method")) {
return ident_method;
}
if (MATCH(var, len, "program")) {
return program;
}
if (MATCH(var, len, "sender_program")) {
return sender_program ? sender_program : program;
}
if (MATCH(var, len, "director")) {
return (addr && addr->director) ? addr->director->name : NULL;
}
if (MATCH(var, len, "router")) {
return (addr && addr->router) ? addr->router->name : NULL;
}
if (MATCH(var, len, "transport")) {
return (addr && addr->transport) ? addr->transport->name : NULL;
}
varname = strncpy(xmalloc(len + 1), var, len);
varname[len] = '\0';
conf_attr = find_config_attribute(varname);
if (conf_attr) {
char *prefix; /* we ignore it in this case */
char *conf_value;
int ret;
unsigned long flags = 0;
DEBUG1(DBG_CONF_HI, "substitute(): found config attr: %s\n", varname);
if (conf_attr->type == t_boolean) {
flags = conf_attr->uptr->v_boolean;
}
/* pretty print it if debugging is enabled */
ret = format_attribute(conf_attr, var, FALSE, &prefix, &conf_value, (char *) NULL, flags);
if (ret) {
xfree(varname);
if (conf_value) {
/* config attrs may contain variable expansions.... */
return conf_value;
} else {
return "";
}
} else {
DEBUG1(DBG_CONF_MID, "substitute(): format_attribute(%v): failed (unknown type?)!\n", varname);
/* XXX panic()? */
}
} else {
DEBUG1(DBG_CONF_MID, "substitute(): unknown variable or config attribute: %v\n", varname);
}
xfree(varname);
return NULL; /* no match */
#undef MATCH
#undef SLEN
}
/*
* lc_fold - meta substitution to convert value to lower case
*/
static char *
lc_fold(value)
register char *value;
{
static char *lc = NULL; /* retained malloc region */
static size_t lc_size; /* keep size of allocated region */
size_t value_size;
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) {
lc = xrealloc(lc, value_size);
lc_size = value_size;
}
p = lc;
while (*value) {
*p++ = tolower((int) *value++);
}
*p = '\0';
return lc;
}
/*
* uc_fold - meta substitution to convert value to upper case
*/
static char *
uc_fold(value)
register char *value;
{
static char *uc = NULL; /* retained malloc region */
static size_t uc_size; /* keep size of allocated region */
size_t value_size;
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) {
uc = xrealloc(uc, value_size);
uc_size = value_size;
}
p = uc;
while (*value) {
*p++ = toupper((unsigned char) *value++);
}
*p = '\0';
return uc;
}
/*
* shell_quote - meta substitution to quote value for shell
*
* Tidies up a string to allow it to be passed to a shell command
* without nasty effects. This makes the whole string single quoted
* (ie one shell argument), and escapes any internal quote characters.
*
* XXX move to string.c?
*/
static char *
shell_quote(value)
register char * value;
{
static struct str wkstr;
static int initialised = FALSE;
char * ptr;
register int chr;
if (!value) {
return NULL;
}
if (!initialised) {
initialised = TRUE;
STR_INIT(&wkstr);
} else {
STR_CHECK(&wkstr);
STR_CLEAR(&wkstr);
}
STR_NEXT(&wkstr, '\'');
for (ptr = value; ((chr = *ptr) != 0); ptr++) {
switch (chr) {
case '\\':
STR_NEXT(&wkstr, chr);
if (! (chr = *++ptr)) { /* premature end of string */
STR_NEXT(&wkstr, '\\');
ptr--; /* will force loop to terminate */
} else {
STR_NEXT(&wkstr, chr);
}
break;
case '\'':
STR_NEXT(&wkstr, '\''); /* end single-quoted */
STR_NEXT(&wkstr, '"'); /* start double-quoted */
STR_NEXT(&wkstr, '\''); /* the single quote */
STR_NEXT(&wkstr, '"'); /* end double-quoted */
STR_NEXT(&wkstr, '\''); /* restart single-quoted */
break;
default:
STR_NEXT(&wkstr, chr);
}
}
STR_NEXT(&wkstr, '\''); /* Trailing quotes */
STR_NEXT(&wkstr, '\0'); /* Trailing NUL */
return STR(&wkstr);
}
/*
* regex_quote - meta substitution to quote value for Regular Expressions
*
* Escapes any RE meta-characters in a string so that it will be matched
* exactly, and not be treated as an RE.
*
* XXX move to string.c?
*/
static char *
regex_quote(value)
register char * value;
{
static struct str wkstr;
static int initialised = FALSE;
register int chr;
if (!value) {
return NULL;
}
if (!initialised) {
initialised = TRUE;
STR_INIT(&wkstr);
} else {
STR_CHECK(&wkstr);
STR_CLEAR(&wkstr);
}
while ((chr = *value++)) {
if (isalnum((int) chr) || chr == ':' || chr == ';') {
STR_NEXT(&wkstr, chr);
} else {
STR_NEXT(&wkstr, '\\');
STR_NEXT(&wkstr, chr);
}
}
STR_NEXT(&wkstr, '\0'); /* Trailing NUL */
return STR(&wkstr);
}
/*
* strip_making_filename - strip string and collapse spaces and dots
*
* i.e.: strip quotes and backslashes from the input string, then remove
* leading `-' characters and collapse any sequence of one or more white space
* characters or `.' characters into a single `.'. (i.e. s|[\. \t\n\r]*|.|g)
*/
static char *
strip_making_filename(value)
char *value;
{
static char *strip_buf = NULL; /* retained malloc region */
static size_t strip_size; /* keep size of allocated region */
size_t value_size;
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) {
strip_buf = xrealloc(strip_buf, value_size);
strip_size = value_size;
}
(void) strcpy(strip_buf, value);
/* first do basic quoted-string to raw C string stripping */
(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((int) *q) || *q == '.') {
while (isspace((int) *++q) || *q == '.') {
;
}
*p++ = '.';
continue;
}
*p++ = *q++;
}
*p = '\0'; /* finish off strip_buf */
return strip_buf;
}
/*
* skip_ident - skip a variable identifier
*/
static char *
skip_ident(s, lim)
char *s, *lim;
{
char *p = s;
while (*p && (lim == NULL || p < lim)) {
if (p != s && !isalnum(*p & 0xFF) && *p != '_') {
break;
}
++p;
}
return p;
}
/*
* clause_token - extract a name or {...} token from the expansion string
*
* Used within ${...} substrings.
*/
static int
clause_token(sp, startp, lenp)
char **sp;
char **startp;
size_t *lenp;
{
char *s = *sp;
while (isspace((int) *s)) {
s++;
}
switch (*s) {
case '\0':
case '}':
return FAIL;
case '{':
s++;
*startp = s;
if (skip_nesting(&s) == FAIL) {
return FAIL;
}
*lenp = s - *startp - 1;
*sp = s;
return SUCCEED;
case ':':
s++;
while (isspace((int) *s)) {
s++;
}
/* FALLTHRU */
default:
*startp = s;
if (*s == ':' || *s == '{' || *s == '}') {
return FAIL; /* XXX syntax error? */
}
s++;
while (*s) {
switch (*s) {
case '_':
case '-':
case '.':
case '/':
s++;
continue;
default:
if (isalnum((int) *s)) {
s++;
continue;
}
break;
}
break;
}
*lenp = s - *startp;
*sp = s;
break;
}
return SUCCEED;
}
static int
skip_nesting(sp)
char **sp;
{
int nesting = 1;
char *s = *sp;
while (nesting) {
switch (*s++) {
case '\0':
bad_subst(*sp, (size_t) 0, "no matching '}'");
return FAIL;
case '{':
++nesting;
break;
case '}':
--nesting;
break;
}
}
*sp = s;
return SUCCEED;
}
/*
* bad_subst - generate a debugging message for a failed substitution.
*/
static void
bad_subst(var, len, msg)
char *var;
size_t len;
char *msg;
{
if (len == 0) {
len = strlen(var);
}
write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY,
"expand_string(): expansion failed for ${%S}%s%s",
len, var,
msg ? ": " : "",
msg ? msg : "");
return;
}
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1