1N/A/*
1N/A * Copyright (c) 1998-2001, 2003, 2006, 2007 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
1N/A * Copyright (c) 1988, 1993
1N/A * The Regents of the University of California. All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A *
1N/A */
1N/A
1N/A#pragma ident "%Z%%M% %I% %E% SMI"
1N/A
1N/A#include <sendmail.h>
1N/A
1N/ASM_RCSID("@(#)$Id: macro.c,v 8.107 2007/08/06 22:29:02 ca Exp $")
1N/A
1N/A#include <sm/sendmail.h>
1N/A#if MAXMACROID != (BITMAPBITS - 1)
1N/A ERROR Read the comment in conf.h
1N/A#endif /* MAXMACROID != (BITMAPBITS - 1) */
1N/A
1N/Astatic char *MacroName[MAXMACROID + 1]; /* macro id to name table */
1N/A
1N/A/*
1N/A** Codes for long named macros.
1N/A** See also macname():
1N/A * if not ASCII printable, look up the name *
1N/A if (n <= 0x20 || n > 0x7f)
1N/A** First use 1 to NEXTMACROID_L, then use NEXTMACROID_H to MAXMACROID.
1N/A*/
1N/A
1N/A#define NEXTMACROID_L 037
1N/A#define NEXTMACROID_H 0240
1N/A
1N/A#if _FFR_MORE_MACROS
1N/A/* table for next id in non-printable ASCII range: disallow some value */
1N/Astatic int NextMIdTable[] =
1N/A{
1N/A /* 0 nul */ 1,
1N/A /* 1 soh */ 2,
1N/A /* 2 stx */ 3,
1N/A /* 3 etx */ 4,
1N/A /* 4 eot */ 5,
1N/A /* 5 enq */ 6,
1N/A /* 6 ack */ 7,
1N/A /* 7 bel */ 8,
1N/A /* 8 bs */ 14,
1N/A /* 9 ht */ -1,
1N/A /* 10 nl */ -1,
1N/A /* 11 vt */ -1,
1N/A /* 12 np */ -1,
1N/A /* 13 cr */ -1,
1N/A /* 14 so */ 15,
1N/A /* 15 si */ 16,
1N/A /* 16 dle */ 17,
1N/A /* 17 dc1 */ 18,
1N/A /* 18 dc2 */ 19,
1N/A /* 19 dc3 */ 20,
1N/A /* 20 dc4 */ 21,
1N/A /* 21 nak */ 22,
1N/A /* 22 syn */ 23,
1N/A /* 23 etb */ 24,
1N/A /* 24 can */ 25,
1N/A /* 25 em */ 26,
1N/A /* 26 sub */ 27,
1N/A /* 27 esc */ 28,
1N/A /* 28 fs */ 29,
1N/A /* 29 gs */ 30,
1N/A /* 30 rs */ 31,
1N/A /* 31 us */ 32,
1N/A /* 32 sp */ -1,
1N/A};
1N/A
1N/A#define NEXTMACROID(mid) ( \
1N/A (mid < NEXTMACROID_L) ? (NextMIdTable[mid]) : \
1N/A ((mid < NEXTMACROID_H) ? NEXTMACROID_H : (mid + 1)))
1N/A
1N/Aint NextMacroId = 1; /* codes for long named macros */
1N/A/* see sendmail.h: Special characters in rewriting rules. */
1N/A#else /* _FFR_MORE_MACROS */
1N/Aint NextMacroId = 0240; /* codes for long named macros */
1N/A#define NEXTMACROID(mid) ((mid) + 1)
1N/A#endif /* _FFR_MORE_MACROS */
1N/A
1N/A
1N/A/*
1N/A** INITMACROS -- initialize the macro system
1N/A**
1N/A** This just involves defining some macros that are actually
1N/A** used internally as metasymbols to be themselves.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** initializes several macros to be themselves.
1N/A*/
1N/A
1N/Astruct metamac MetaMacros[] =
1N/A{
1N/A /* LHS pattern matching characters */
1N/A { '*', MATCHZANY }, { '+', MATCHANY }, { '-', MATCHONE },
1N/A { '=', MATCHCLASS }, { '~', MATCHNCLASS },
1N/A
1N/A /* these are RHS metasymbols */
1N/A { '#', CANONNET }, { '@', CANONHOST }, { ':', CANONUSER },
1N/A { '>', CALLSUBR },
1N/A
1N/A /* the conditional operations */
1N/A { '?', CONDIF }, { '|', CONDELSE }, { '.', CONDFI },
1N/A
1N/A /* the hostname lookup characters */
1N/A { '[', HOSTBEGIN }, { ']', HOSTEND },
1N/A { '(', LOOKUPBEGIN }, { ')', LOOKUPEND },
1N/A
1N/A /* miscellaneous control characters */
1N/A { '&', MACRODEXPAND },
1N/A
1N/A { '\0', '\0' }
1N/A};
1N/A
1N/A#define MACBINDING(name, mid) \
1N/A stab(name, ST_MACRO, ST_ENTER)->s_macro = mid; \
1N/A MacroName[mid] = name;
1N/A
1N/Avoid
1N/Ainitmacros(e)
1N/A ENVELOPE *e;
1N/A{
1N/A struct metamac *m;
1N/A int c;
1N/A char buf[5];
1N/A
1N/A for (m = MetaMacros; m->metaname != '\0'; m++)
1N/A {
1N/A buf[0] = m->metaval;
1N/A buf[1] = '\0';
1N/A macdefine(&e->e_macro, A_TEMP, m->metaname, buf);
1N/A }
1N/A buf[0] = MATCHREPL;
1N/A buf[2] = '\0';
1N/A for (c = '0'; c <= '9'; c++)
1N/A {
1N/A buf[1] = c;
1N/A macdefine(&e->e_macro, A_TEMP, c, buf);
1N/A }
1N/A
1N/A /* set defaults for some macros sendmail will use later */
1N/A macdefine(&e->e_macro, A_PERM, 'n', "MAILER-DAEMON");
1N/A
1N/A /* set up external names for some internal macros */
1N/A MACBINDING("opMode", MID_OPMODE);
1N/A /*XXX should probably add equivalents for all short macros here XXX*/
1N/A}
1N/A
1N/A/*
1N/A** EXPAND/DOEXPAND -- macro expand a string using $x escapes.
1N/A**
1N/A** After expansion, the expansion will be in external form (that is,
1N/A** there will be no sendmail metacharacters and METAQUOTEs will have
1N/A** been stripped out).
1N/A**
1N/A** Parameters:
1N/A** s -- the string to expand.
1N/A** buf -- the place to put the expansion.
1N/A** bufsize -- the size of the buffer.
1N/A** explevel -- the depth of expansion (doexpand only)
1N/A** e -- envelope in which to work.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** none.
1N/A*/
1N/A
1N/Astatic void doexpand __P(( char *, char *, size_t, int, ENVELOPE *));
1N/A
1N/Astatic void
1N/Adoexpand(s, buf, bufsize, explevel, e)
1N/A char *s;
1N/A char *buf;
1N/A size_t bufsize;
1N/A int explevel;
1N/A ENVELOPE *e;
1N/A{
1N/A char *xp;
1N/A char *q;
1N/A bool skipping; /* set if conditionally skipping output */
1N/A bool recurse; /* set if recursion required */
1N/A size_t i;
1N/A int skiplev; /* skipping nesting level */
1N/A int iflev; /* if nesting level */
1N/A bool quotenext; /* quote the following character */
1N/A char xbuf[MACBUFSIZE];
1N/A
1N/A if (tTd(35, 24))
1N/A {
1N/A sm_dprintf("expand(");
1N/A xputs(sm_debug_file(), s);
1N/A sm_dprintf(")\n");
1N/A }
1N/A
1N/A recurse = false;
1N/A skipping = false;
1N/A skiplev = 0;
1N/A iflev = 0;
1N/A quotenext = false;
1N/A if (s == NULL)
1N/A s = "";
1N/A for (xp = xbuf; *s != '\0'; s++)
1N/A {
1N/A int c;
1N/A
1N/A /*
1N/A ** Check for non-ordinary (special?) character.
1N/A ** 'q' will be the interpolated quantity.
1N/A */
1N/A
1N/A q = NULL;
1N/A c = *s & 0377;
1N/A
1N/A if (quotenext)
1N/A {
1N/A quotenext = false;
1N/A goto simpleinterpolate;
1N/A }
1N/A
1N/A switch (c)
1N/A {
1N/A case CONDIF: /* see if var set */
1N/A iflev++;
1N/A c = *++s & 0377;
1N/A if (skipping)
1N/A skiplev++;
1N/A else
1N/A {
1N/A char *mv;
1N/A
1N/A mv = macvalue(c, e);
1N/A skipping = (mv == NULL || *mv == '\0');
1N/A }
1N/A continue;
1N/A
1N/A case CONDELSE: /* change state of skipping */
1N/A if (iflev == 0)
1N/A break; /* XXX: error */
1N/A if (skiplev == 0)
1N/A skipping = !skipping;
1N/A continue;
1N/A
1N/A case CONDFI: /* stop skipping */
1N/A if (iflev == 0)
1N/A break; /* XXX: error */
1N/A iflev--;
1N/A if (skiplev == 0)
1N/A skipping = false;
1N/A if (skipping)
1N/A skiplev--;
1N/A continue;
1N/A
1N/A case MACROEXPAND: /* macro interpolation */
1N/A c = bitidx(*++s);
1N/A if (c != '\0')
1N/A q = macvalue(c, e);
1N/A else
1N/A {
1N/A s--;
1N/A q = NULL;
1N/A }
1N/A if (q == NULL)
1N/A continue;
1N/A break;
1N/A
1N/A case METAQUOTE:
1N/A /* next octet completely quoted */
1N/A quotenext = true;
1N/A break;
1N/A }
1N/A
1N/A /*
1N/A ** Interpolate q or output one character
1N/A */
1N/A
1N/A simpleinterpolate:
1N/A if (skipping || xp >= &xbuf[sizeof(xbuf) - 1])
1N/A continue;
1N/A if (q == NULL)
1N/A *xp++ = c;
1N/A else
1N/A {
1N/A /* copy to end of q or max space remaining in buf */
1N/A bool hiderecurse = false;
1N/A
1N/A while ((c = *q++) != '\0' &&
1N/A xp < &xbuf[sizeof(xbuf) - 1])
1N/A {
1N/A /* check for any sendmail metacharacters */
1N/A if (!hiderecurse && (c & 0340) == 0200)
1N/A recurse = true;
1N/A *xp++ = c;
1N/A
1N/A /* give quoted characters a free ride */
1N/A hiderecurse = (c & 0377) == METAQUOTE;
1N/A }
1N/A }
1N/A }
1N/A *xp = '\0';
1N/A
1N/A if (tTd(35, 28))
1N/A {
1N/A sm_dprintf("expand(%d) ==> ", explevel);
1N/A xputs(sm_debug_file(), xbuf);
1N/A sm_dprintf("\n");
1N/A }
1N/A
1N/A /* recurse as appropriate */
1N/A if (recurse)
1N/A {
1N/A if (explevel < MaxMacroRecursion)
1N/A {
1N/A doexpand(xbuf, buf, bufsize, explevel + 1, e);
1N/A return;
1N/A }
1N/A syserr("expand: recursion too deep (%d max)",
1N/A MaxMacroRecursion);
1N/A }
1N/A
1N/A /* copy results out */
1N/A if (explevel == 0)
1N/A (void) sm_strlcpy(buf, xbuf, bufsize);
1N/A else
1N/A {
1N/A /* leave in internal form */
1N/A i = xp - xbuf;
1N/A if (i >= bufsize)
1N/A i = bufsize - 1;
1N/A memmove(buf, xbuf, i);
1N/A buf[i] = '\0';
1N/A }
1N/A
1N/A if (tTd(35, 24))
1N/A {
1N/A sm_dprintf("expand ==> ");
1N/A xputs(sm_debug_file(), buf);
1N/A sm_dprintf("\n");
1N/A }
1N/A}
1N/A
1N/Avoid
1N/Aexpand(s, buf, bufsize, e)
1N/A char *s;
1N/A char *buf;
1N/A size_t bufsize;
1N/A ENVELOPE *e;
1N/A{
1N/A doexpand(s, buf, bufsize, 0, e);
1N/A}
1N/A
1N/A/*
1N/A** MACDEFINE -- bind a macro name to a value
1N/A**
1N/A** Set a macro to a value, with fancy storage management.
1N/A** macdefine will make a copy of the value, if required,
1N/A** and will ensure that the storage for the previous value
1N/A** is not leaked.
1N/A**
1N/A** Parameters:
1N/A** mac -- Macro table.
1N/A** vclass -- storage class of 'value', ignored if value==NULL.
1N/A** A_HEAP means that the value was allocated by
1N/A** malloc, and that macdefine owns the storage.
1N/A** A_TEMP means that value points to temporary storage,
1N/A** and thus macdefine needs to make a copy.
1N/A** A_PERM means that value points to storage that
1N/A** will remain allocated and unchanged for
1N/A** at least the lifetime of mac. Use A_PERM if:
1N/A** -- value == NULL,
1N/A** -- value points to a string literal,
1N/A** -- value was allocated from mac->mac_rpool
1N/A** or (in the case of an envelope macro)
1N/A** from e->e_rpool,
1N/A** -- in the case of an envelope macro,
1N/A** value is a string member of the envelope
1N/A** such as e->e_sender.
1N/A** id -- Macro id. This is a single character macro name
1N/A** such as 'g', or a value returned by macid().
1N/A** value -- Macro value: either NULL, or a string.
1N/A*/
1N/A
1N/Avoid
1N/A#if SM_HEAP_CHECK
1N/Amacdefine_tagged(mac, vclass, id, value, file, line, grp)
1N/A#else /* SM_HEAP_CHECK */
1N/Amacdefine(mac, vclass, id, value)
1N/A#endif /* SM_HEAP_CHECK */
1N/A MACROS_T *mac;
1N/A ARGCLASS_T vclass;
1N/A int id;
1N/A char *value;
1N/A#if SM_HEAP_CHECK
1N/A char *file;
1N/A int line;
1N/A int grp;
1N/A#endif /* SM_HEAP_CHECK */
1N/A{
1N/A char *newvalue;
1N/A
1N/A if (id < 0 || id > MAXMACROID)
1N/A return;
1N/A
1N/A if (tTd(35, 9))
1N/A {
1N/A sm_dprintf("%sdefine(%s as ",
1N/A mac->mac_table[id] == NULL ? "" : "re", macname(id));
1N/A xputs(sm_debug_file(), value);
1N/A sm_dprintf(")\n");
1N/A }
1N/A
1N/A if (mac->mac_rpool == NULL)
1N/A {
1N/A char *freeit = NULL;
1N/A
1N/A if (mac->mac_table[id] != NULL &&
1N/A bitnset(id, mac->mac_allocated))
1N/A freeit = mac->mac_table[id];
1N/A
1N/A if (value == NULL || vclass == A_HEAP)
1N/A {
1N/A sm_heap_checkptr_tagged(value, file, line);
1N/A newvalue = value;
1N/A clrbitn(id, mac->mac_allocated);
1N/A }
1N/A else
1N/A {
1N/A#if SM_HEAP_CHECK
1N/A newvalue = sm_strdup_tagged_x(value, file, line, 0);
1N/A#else /* SM_HEAP_CHECK */
1N/A newvalue = sm_strdup_x(value);
1N/A#endif /* SM_HEAP_CHECK */
1N/A setbitn(id, mac->mac_allocated);
1N/A }
1N/A mac->mac_table[id] = newvalue;
1N/A if (freeit != NULL)
1N/A sm_free(freeit);
1N/A }
1N/A else
1N/A {
1N/A if (value == NULL || vclass == A_PERM)
1N/A newvalue = value;
1N/A else
1N/A newvalue = sm_rpool_strdup_x(mac->mac_rpool, value);
1N/A mac->mac_table[id] = newvalue;
1N/A if (vclass == A_HEAP)
1N/A sm_free(value);
1N/A }
1N/A
1N/A#if _FFR_RESET_MACRO_GLOBALS
1N/A switch (id)
1N/A {
1N/A case 'j':
1N/A PSTRSET(MyHostName, value);
1N/A break;
1N/A }
1N/A#endif /* _FFR_RESET_MACRO_GLOBALS */
1N/A}
1N/A
1N/A/*
1N/A** MACSET -- set a named macro to a value (low level)
1N/A**
1N/A** No fancy storage management; the caller takes full responsibility.
1N/A** Often used with macget; see also macdefine.
1N/A**
1N/A** Parameters:
1N/A** mac -- Macro table.
1N/A** i -- Macro name, specified as an integer offset.
1N/A** value -- Macro value: either NULL, or a string.
1N/A*/
1N/A
1N/Avoid
1N/Amacset(mac, i, value)
1N/A MACROS_T *mac;
1N/A int i;
1N/A char *value;
1N/A{
1N/A if (i < 0 || i > MAXMACROID)
1N/A return;
1N/A
1N/A if (tTd(35, 9))
1N/A {
1N/A sm_dprintf("macset(%s as ", macname(i));
1N/A xputs(sm_debug_file(), value);
1N/A sm_dprintf(")\n");
1N/A }
1N/A mac->mac_table[i] = value;
1N/A}
1N/A
1N/A/*
1N/A** MACVALUE -- return uninterpreted value of a macro.
1N/A**
1N/A** Does fancy path searching.
1N/A** The low level counterpart is macget.
1N/A**
1N/A** Parameters:
1N/A** n -- the name of the macro.
1N/A** e -- envelope in which to start looking for the macro.
1N/A**
1N/A** Returns:
1N/A** The value of n.
1N/A**
1N/A** Side Effects:
1N/A** none.
1N/A*/
1N/A
1N/Achar *
1N/Amacvalue(n, e)
1N/A int n;
1N/A ENVELOPE *e;
1N/A{
1N/A n = bitidx(n);
1N/A if (e != NULL && e->e_mci != NULL)
1N/A {
1N/A char *p = e->e_mci->mci_macro.mac_table[n];
1N/A
1N/A if (p != NULL)
1N/A return p;
1N/A }
1N/A while (e != NULL)
1N/A {
1N/A char *p = e->e_macro.mac_table[n];
1N/A
1N/A if (p != NULL)
1N/A return p;
1N/A if (e == e->e_parent)
1N/A break;
1N/A e = e->e_parent;
1N/A }
1N/A return GlobalMacros.mac_table[n];
1N/A}
1N/A
1N/A/*
1N/A** MACNAME -- return the name of a macro given its internal id
1N/A**
1N/A** Parameter:
1N/A** n -- the id of the macro
1N/A**
1N/A** Returns:
1N/A** The name of n.
1N/A**
1N/A** Side Effects:
1N/A** none.
1N/A**
1N/A** WARNING:
1N/A** Not thread-safe.
1N/A*/
1N/A
1N/Achar *
1N/Amacname(n)
1N/A int n;
1N/A{
1N/A static char mbuf[2];
1N/A
1N/A n = (int)(unsigned char)n;
1N/A if (n > MAXMACROID)
1N/A return "***OUT OF RANGE MACRO***";
1N/A
1N/A /* if not ASCII printable, look up the name */
1N/A if (n <= 0x20 || n > 0x7f)
1N/A {
1N/A char *p = MacroName[n];
1N/A
1N/A if (p != NULL)
1N/A return p;
1N/A return "***UNDEFINED MACRO***";
1N/A }
1N/A
1N/A /* if in the ASCII graphic range, just return the id directly */
1N/A mbuf[0] = n;
1N/A mbuf[1] = '\0';
1N/A return mbuf;
1N/A}
1N/A
1N/A/*
1N/A** MACID_PARSE -- return id of macro identified by its name
1N/A**
1N/A** Parameters:
1N/A** p -- pointer to name string -- either a single
1N/A** character or {name}.
1N/A** ep -- filled in with the pointer to the byte
1N/A** after the name.
1N/A**
1N/A** Returns:
1N/A** 0 -- An error was detected.
1N/A** 1..MAXMACROID -- The internal id code for this macro.
1N/A**
1N/A** Side Effects:
1N/A** If this is a new macro name, a new id is allocated.
1N/A** On error, syserr is called.
1N/A*/
1N/A
1N/Aint
1N/Amacid_parse(p, ep)
1N/A char *p;
1N/A char **ep;
1N/A{
1N/A int mid;
1N/A char *bp;
1N/A char mbuf[MAXMACNAMELEN + 1];
1N/A
1N/A if (tTd(35, 14))
1N/A {
1N/A sm_dprintf("macid(");
1N/A xputs(sm_debug_file(), p);
1N/A sm_dprintf(") => ");
1N/A }
1N/A
1N/A if (*p == '\0' || (p[0] == '{' && p[1] == '}'))
1N/A {
1N/A syserr("Name required for macro/class");
1N/A if (ep != NULL)
1N/A *ep = p;
1N/A if (tTd(35, 14))
1N/A sm_dprintf("NULL\n");
1N/A return 0;
1N/A }
1N/A if (*p != '{')
1N/A {
1N/A /* the macro is its own code */
1N/A if (ep != NULL)
1N/A *ep = p + 1;
1N/A if (tTd(35, 14))
1N/A {
1N/A char buf[2];
1N/A
1N/A buf[0] = *p;
1N/A buf[1] = '\0';
1N/A xputs(sm_debug_file(), buf);
1N/A sm_dprintf("\n");
1N/A }
1N/A return bitidx(*p);
1N/A }
1N/A bp = mbuf;
1N/A while (*++p != '\0' && *p != '}' && bp < &mbuf[sizeof(mbuf) - 1])
1N/A {
1N/A if (isascii(*p) && (isalnum(*p) || *p == '_'))
1N/A *bp++ = *p;
1N/A else
1N/A syserr("Invalid macro/class character %c", *p);
1N/A }
1N/A *bp = '\0';
1N/A mid = -1;
1N/A if (*p == '\0')
1N/A {
1N/A syserr("Unbalanced { on %s", mbuf); /* missing } */
1N/A }
1N/A else if (*p != '}')
1N/A {
1N/A syserr("Macro/class name ({%s}) too long (%d chars max)",
1N/A mbuf, (int) (sizeof(mbuf) - 1));
1N/A }
1N/A else if (mbuf[1] == '\0' && mbuf[0] >= 0x20)
1N/A {
1N/A /* ${x} == $x */
1N/A mid = bitidx(mbuf[0]);
1N/A p++;
1N/A }
1N/A else
1N/A {
1N/A STAB *s;
1N/A
1N/A s = stab(mbuf, ST_MACRO, ST_ENTER);
1N/A if (s->s_macro != 0)
1N/A mid = s->s_macro;
1N/A else
1N/A {
1N/A if (NextMacroId > MAXMACROID)
1N/A {
1N/A syserr("Macro/class {%s}: too many long names",
1N/A mbuf);
1N/A s->s_macro = -1;
1N/A }
1N/A else
1N/A {
1N/A MacroName[NextMacroId] = s->s_name;
1N/A s->s_macro = mid = NextMacroId;
1N/A NextMacroId = NEXTMACROID(NextMacroId);
1N/A }
1N/A }
1N/A p++;
1N/A }
1N/A if (ep != NULL)
1N/A *ep = p;
1N/A if (mid < 0 || mid > MAXMACROID)
1N/A {
1N/A syserr("Unable to assign macro/class ID (mid = 0x%x)", mid);
1N/A if (tTd(35, 14))
1N/A sm_dprintf("NULL\n");
1N/A return 0;
1N/A }
1N/A if (tTd(35, 14))
1N/A sm_dprintf("0x%x\n", mid);
1N/A return mid;
1N/A}
1N/A
1N/A/*
1N/A** WORDINCLASS -- tell if a word is in a specific class
1N/A**
1N/A** Parameters:
1N/A** str -- the name of the word to look up.
1N/A** cl -- the class name.
1N/A**
1N/A** Returns:
1N/A** true if str can be found in cl.
1N/A** false otherwise.
1N/A*/
1N/A
1N/Abool
1N/Awordinclass(str, cl)
1N/A char *str;
1N/A int cl;
1N/A{
1N/A STAB *s;
1N/A
1N/A s = stab(str, ST_CLASS, ST_FIND);
1N/A return s != NULL && bitnset(bitidx(cl), s->s_class);
1N/A}