root/usr/src/cmd/sendmail/src/macro.c
/*
 * Copyright (c) 1998-2001, 2003, 2006, 2007 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include <sendmail.h>

SM_RCSID("@(#)$Id: macro.c,v 8.107 2007/08/06 22:29:02 ca Exp $")

#include <sm/sendmail.h>
#if MAXMACROID != (BITMAPBITS - 1)
        ERROR Read the comment in conf.h
#endif /* MAXMACROID != (BITMAPBITS - 1) */

static char     *MacroName[MAXMACROID + 1];     /* macro id to name table */

/*
**  Codes for long named macros.
**  See also macname():
        * if not ASCII printable, look up the name *
        if (n <= 0x20 || n > 0x7f)
**  First use 1 to NEXTMACROID_L, then use NEXTMACROID_H to MAXMACROID.
*/

#define NEXTMACROID_L 037
#define NEXTMACROID_H 0240

#if _FFR_MORE_MACROS
/* table for next id in non-printable ASCII range: disallow some value */
static int NextMIdTable[] =
{
        /*  0  nul */    1,
        /*  1  soh */    2,
        /*  2  stx */    3,
        /*  3  etx */    4,
        /*  4  eot */    5,
        /*  5  enq */    6,
        /*  6  ack */    7,
        /*  7  bel */    8,
        /*  8  bs  */   14,
        /*  9  ht  */   -1,
        /* 10  nl  */   -1,
        /* 11  vt  */   -1,
        /* 12  np  */   -1,
        /* 13  cr  */   -1,
        /* 14  so  */   15,
        /* 15  si  */   16,
        /* 16  dle */   17,
        /* 17  dc1 */   18,
        /* 18  dc2 */   19,
        /* 19  dc3 */   20,
        /* 20  dc4 */   21,
        /* 21  nak */   22,
        /* 22  syn */   23,
        /* 23  etb */   24,
        /* 24  can */   25,
        /* 25  em  */   26,
        /* 26  sub */   27,
        /* 27  esc */   28,
        /* 28  fs  */   29,
        /* 29  gs  */   30,
        /* 30  rs  */   31,
        /* 31  us  */   32,
        /* 32  sp  */   -1,
};

#define NEXTMACROID(mid)        (               \
        (mid < NEXTMACROID_L) ? (NextMIdTable[mid]) :   \
        ((mid < NEXTMACROID_H) ? NEXTMACROID_H : (mid + 1)))

int             NextMacroId = 1;        /* codes for long named macros */
/* see sendmail.h: Special characters in rewriting rules. */
#else /* _FFR_MORE_MACROS */
int             NextMacroId = 0240;     /* codes for long named macros */
#define NEXTMACROID(mid)        ((mid) + 1)
#endif /* _FFR_MORE_MACROS */


/*
**  INITMACROS -- initialize the macro system
**
**      This just involves defining some macros that are actually
**      used internally as metasymbols to be themselves.
**
**      Parameters:
**              none.
**
**      Returns:
**              none.
**
**      Side Effects:
**              initializes several macros to be themselves.
*/

struct metamac  MetaMacros[] =
{
        /* LHS pattern matching characters */
        { '*', MATCHZANY },     { '+', MATCHANY },      { '-', MATCHONE },
        { '=', MATCHCLASS },    { '~', MATCHNCLASS },

        /* these are RHS metasymbols */
        { '#', CANONNET },      { '@', CANONHOST },     { ':', CANONUSER },
        { '>', CALLSUBR },

        /* the conditional operations */
        { '?', CONDIF },        { '|', CONDELSE },      { '.', CONDFI },

        /* the hostname lookup characters */
        { '[', HOSTBEGIN },     { ']', HOSTEND },
        { '(', LOOKUPBEGIN },   { ')', LOOKUPEND },

        /* miscellaneous control characters */
        { '&', MACRODEXPAND },

        { '\0', '\0' }
};

#define MACBINDING(name, mid) \
                stab(name, ST_MACRO, ST_ENTER)->s_macro = mid; \
                MacroName[mid] = name;

void
initmacros(e)
        ENVELOPE *e;
{
        struct metamac *m;
        int c;
        char buf[5];

        for (m = MetaMacros; m->metaname != '\0'; m++)
        {
                buf[0] = m->metaval;
                buf[1] = '\0';
                macdefine(&e->e_macro, A_TEMP, m->metaname, buf);
        }
        buf[0] = MATCHREPL;
        buf[2] = '\0';
        for (c = '0'; c <= '9'; c++)
        {
                buf[1] = c;
                macdefine(&e->e_macro, A_TEMP, c, buf);
        }

        /* set defaults for some macros sendmail will use later */
        macdefine(&e->e_macro, A_PERM, 'n', "MAILER-DAEMON");

        /* set up external names for some internal macros */
        MACBINDING("opMode", MID_OPMODE);
        /*XXX should probably add equivalents for all short macros here XXX*/
}

/*
**  EXPAND/DOEXPAND -- macro expand a string using $x escapes.
**
**      After expansion, the expansion will be in external form (that is,
**      there will be no sendmail metacharacters and METAQUOTEs will have
**      been stripped out).
**
**      Parameters:
**              s -- the string to expand.
**              buf -- the place to put the expansion.
**              bufsize -- the size of the buffer.
**              explevel -- the depth of expansion (doexpand only)
**              e -- envelope in which to work.
**
**      Returns:
**              none.
**
**      Side Effects:
**              none.
*/

static void doexpand __P(( char *, char *, size_t, int, ENVELOPE *));

static void
doexpand(s, buf, bufsize, explevel, e)
        char *s;
        char *buf;
        size_t bufsize;
        int explevel;
        ENVELOPE *e;
{
        char *xp;
        char *q;
        bool skipping;          /* set if conditionally skipping output */
        bool recurse;           /* set if recursion required */
        size_t i;
        int skiplev;            /* skipping nesting level */
        int iflev;              /* if nesting level */
        bool quotenext;         /* quote the following character */
        char xbuf[MACBUFSIZE];

        if (tTd(35, 24))
        {
                sm_dprintf("expand(");
                xputs(sm_debug_file(), s);
                sm_dprintf(")\n");
        }

        recurse = false;
        skipping = false;
        skiplev = 0;
        iflev = 0;
        quotenext = false;
        if (s == NULL)
                s = "";
        for (xp = xbuf; *s != '\0'; s++)
        {
                int c;

                /*
                **  Check for non-ordinary (special?) character.
                **      'q' will be the interpolated quantity.
                */

                q = NULL;
                c = *s & 0377;

                if (quotenext)
                {
                        quotenext = false;
                        goto simpleinterpolate;
                }

                switch (c)
                {
                  case CONDIF:          /* see if var set */
                        iflev++;
                        c = *++s & 0377;
                        if (skipping)
                                skiplev++;
                        else
                        {
                                char *mv;

                                mv = macvalue(c, e);
                                skipping = (mv == NULL || *mv == '\0');
                        }
                        continue;

                  case CONDELSE:        /* change state of skipping */
                        if (iflev == 0)
                                break;  /* XXX: error */
                        if (skiplev == 0)
                                skipping = !skipping;
                        continue;

                  case CONDFI:          /* stop skipping */
                        if (iflev == 0)
                                break;  /* XXX: error */
                        iflev--;
                        if (skiplev == 0)
                                skipping = false;
                        if (skipping)
                                skiplev--;
                        continue;

                  case MACROEXPAND:     /* macro interpolation */
                        c = bitidx(*++s);
                        if (c != '\0')
                                q = macvalue(c, e);
                        else
                        {
                                s--;
                                q = NULL;
                        }
                        if (q == NULL)
                                continue;
                        break;

                  case METAQUOTE:
                        /* next octet completely quoted */
                        quotenext = true;
                        break;
                }

                /*
                **  Interpolate q or output one character
                */

  simpleinterpolate:
                if (skipping || xp >= &xbuf[sizeof(xbuf) - 1])
                        continue;
                if (q == NULL)
                        *xp++ = c;
                else
                {
                        /* copy to end of q or max space remaining in buf */
                        bool hiderecurse = false;

                        while ((c = *q++) != '\0' &&
                                xp < &xbuf[sizeof(xbuf) - 1])
                        {
                                /* check for any sendmail metacharacters */
                                if (!hiderecurse && (c & 0340) == 0200)
                                        recurse = true;
                                *xp++ = c;

                                /* give quoted characters a free ride */
                                hiderecurse = (c & 0377) == METAQUOTE;
                        }
                }
        }
        *xp = '\0';

        if (tTd(35, 28))
        {
                sm_dprintf("expand(%d) ==> ", explevel);
                xputs(sm_debug_file(), xbuf);
                sm_dprintf("\n");
        }

        /* recurse as appropriate */
        if (recurse)
        {
                if (explevel < MaxMacroRecursion)
                {
                        doexpand(xbuf, buf, bufsize, explevel + 1, e);
                        return;
                }
                syserr("expand: recursion too deep (%d max)",
                        MaxMacroRecursion);
        }

        /* copy results out */
        if (explevel == 0)
                (void) sm_strlcpy(buf, xbuf, bufsize);
        else
        {
                /* leave in internal form */
                i = xp - xbuf;
                if (i >= bufsize)
                        i = bufsize - 1;
                memmove(buf, xbuf, i);
                buf[i] = '\0';
        }

        if (tTd(35, 24))
        {
                sm_dprintf("expand ==> ");
                xputs(sm_debug_file(), buf);
                sm_dprintf("\n");
        }
}

void
expand(s, buf, bufsize, e)
        char *s;
        char *buf;
        size_t bufsize;
        ENVELOPE *e;
{
        doexpand(s, buf, bufsize, 0, e);
}

/*
**  MACDEFINE -- bind a macro name to a value
**
**      Set a macro to a value, with fancy storage management.
**      macdefine will make a copy of the value, if required,
**      and will ensure that the storage for the previous value
**      is not leaked.
**
**      Parameters:
**              mac -- Macro table.
**              vclass -- storage class of 'value', ignored if value==NULL.
**                      A_HEAP  means that the value was allocated by
**                              malloc, and that macdefine owns the storage.
**                      A_TEMP  means that value points to temporary storage,
**                              and thus macdefine needs to make a copy.
**                      A_PERM  means that value points to storage that
**                              will remain allocated and unchanged for
**                              at least the lifetime of mac.  Use A_PERM if:
**                              -- value == NULL,
**                              -- value points to a string literal,
**                              -- value was allocated from mac->mac_rpool
**                                 or (in the case of an envelope macro)
**                                 from e->e_rpool,
**                              -- in the case of an envelope macro,
**                                 value is a string member of the envelope
**                                 such as e->e_sender.
**              id -- Macro id.  This is a single character macro name
**                      such as 'g', or a value returned by macid().
**              value -- Macro value: either NULL, or a string.
*/

void
#if SM_HEAP_CHECK
macdefine_tagged(mac, vclass, id, value, file, line, grp)
#else /* SM_HEAP_CHECK */
macdefine(mac, vclass, id, value)
#endif /* SM_HEAP_CHECK */
        MACROS_T *mac;
        ARGCLASS_T vclass;
        int id;
        char *value;
#if SM_HEAP_CHECK
        char *file;
        int line;
        int grp;
#endif /* SM_HEAP_CHECK */
{
        char *newvalue;

        if (id < 0 || id > MAXMACROID)
                return;

        if (tTd(35, 9))
        {
                sm_dprintf("%sdefine(%s as ",
                        mac->mac_table[id] == NULL ? "" : "re", macname(id));
                xputs(sm_debug_file(), value);
                sm_dprintf(")\n");
        }

        if (mac->mac_rpool == NULL)
        {
                char *freeit = NULL;

                if (mac->mac_table[id] != NULL &&
                    bitnset(id, mac->mac_allocated))
                        freeit = mac->mac_table[id];

                if (value == NULL || vclass == A_HEAP)
                {
                        sm_heap_checkptr_tagged(value, file, line);
                        newvalue = value;
                        clrbitn(id, mac->mac_allocated);
                }
                else
                {
#if SM_HEAP_CHECK
                        newvalue = sm_strdup_tagged_x(value, file, line, 0);
#else /* SM_HEAP_CHECK */
                        newvalue = sm_strdup_x(value);
#endif /* SM_HEAP_CHECK */
                        setbitn(id, mac->mac_allocated);
                }
                mac->mac_table[id] = newvalue;
                if (freeit != NULL)
                        sm_free(freeit);
        }
        else
        {
                if (value == NULL || vclass == A_PERM)
                        newvalue = value;
                else
                        newvalue = sm_rpool_strdup_x(mac->mac_rpool, value);
                mac->mac_table[id] = newvalue;
                if (vclass == A_HEAP)
                        sm_free(value);
        }

#if _FFR_RESET_MACRO_GLOBALS
        switch (id)
        {
          case 'j':
                PSTRSET(MyHostName, value);
                break;
        }
#endif /* _FFR_RESET_MACRO_GLOBALS */
}

/*
**  MACSET -- set a named macro to a value (low level)
**
**      No fancy storage management; the caller takes full responsibility.
**      Often used with macget; see also macdefine.
**
**      Parameters:
**              mac -- Macro table.
**              i -- Macro name, specified as an integer offset.
**              value -- Macro value: either NULL, or a string.
*/

void
macset(mac, i, value)
        MACROS_T *mac;
        int i;
        char *value;
{
        if (i < 0 || i > MAXMACROID)
                return;

        if (tTd(35, 9))
        {
                sm_dprintf("macset(%s as ", macname(i));
                xputs(sm_debug_file(), value);
                sm_dprintf(")\n");
        }
        mac->mac_table[i] = value;
}

/*
**  MACVALUE -- return uninterpreted value of a macro.
**
**      Does fancy path searching.
**      The low level counterpart is macget.
**
**      Parameters:
**              n -- the name of the macro.
**              e -- envelope in which to start looking for the macro.
**
**      Returns:
**              The value of n.
**
**      Side Effects:
**              none.
*/

char *
macvalue(n, e)
        int n;
        ENVELOPE *e;
{
        n = bitidx(n);
        if (e != NULL && e->e_mci != NULL)
        {
                char *p = e->e_mci->mci_macro.mac_table[n];

                if (p != NULL)
                        return p;
        }
        while (e != NULL)
        {
                char *p = e->e_macro.mac_table[n];

                if (p != NULL)
                        return p;
                if (e == e->e_parent)
                        break;
                e = e->e_parent;
        }
        return GlobalMacros.mac_table[n];
}

/*
**  MACNAME -- return the name of a macro given its internal id
**
**      Parameter:
**              n -- the id of the macro
**
**      Returns:
**              The name of n.
**
**      Side Effects:
**              none.
**
**      WARNING:
**              Not thread-safe.
*/

char *
macname(n)
        int n;
{
        static char mbuf[2];

        n = (int)(unsigned char)n;
        if (n > MAXMACROID)
                return "***OUT OF RANGE MACRO***";

        /* if not ASCII printable, look up the name */
        if (n <= 0x20 || n > 0x7f)
        {
                char *p = MacroName[n];

                if (p != NULL)
                        return p;
                return "***UNDEFINED MACRO***";
        }

        /* if in the ASCII graphic range, just return the id directly */
        mbuf[0] = n;
        mbuf[1] = '\0';
        return mbuf;
}

/*
**  MACID_PARSE -- return id of macro identified by its name
**
**      Parameters:
**              p -- pointer to name string -- either a single
**                      character or {name}.
**              ep -- filled in with the pointer to the byte
**                      after the name.
**
**      Returns:
**              0 -- An error was detected.
**              1..MAXMACROID -- The internal id code for this macro.
**
**      Side Effects:
**              If this is a new macro name, a new id is allocated.
**              On error, syserr is called.
*/

int
macid_parse(p, ep)
        char *p;
        char **ep;
{
        int mid;
        char *bp;
        char mbuf[MAXMACNAMELEN + 1];

        if (tTd(35, 14))
        {
                sm_dprintf("macid(");
                xputs(sm_debug_file(), p);
                sm_dprintf(") => ");
        }

        if (*p == '\0' || (p[0] == '{' && p[1] == '}'))
        {
                syserr("Name required for macro/class");
                if (ep != NULL)
                        *ep = p;
                if (tTd(35, 14))
                        sm_dprintf("NULL\n");
                return 0;
        }
        if (*p != '{')
        {
                /* the macro is its own code */
                if (ep != NULL)
                        *ep = p + 1;
                if (tTd(35, 14))
                {
                        char buf[2];

                        buf[0] = *p;
                        buf[1] = '\0';
                        xputs(sm_debug_file(), buf);
                        sm_dprintf("\n");
                }
                return bitidx(*p);
        }
        bp = mbuf;
        while (*++p != '\0' && *p != '}' && bp < &mbuf[sizeof(mbuf) - 1])
        {
                if (isascii(*p) && (isalnum(*p) || *p == '_'))
                        *bp++ = *p;
                else
                        syserr("Invalid macro/class character %c", *p);
        }
        *bp = '\0';
        mid = -1;
        if (*p == '\0')
        {
                syserr("Unbalanced { on %s", mbuf);     /* missing } */
        }
        else if (*p != '}')
        {
                syserr("Macro/class name ({%s}) too long (%d chars max)",
                        mbuf, (int) (sizeof(mbuf) - 1));
        }
        else if (mbuf[1] == '\0' && mbuf[0] >= 0x20)
        {
                /* ${x} == $x */
                mid = bitidx(mbuf[0]);
                p++;
        }
        else
        {
                STAB *s;

                s = stab(mbuf, ST_MACRO, ST_ENTER);
                if (s->s_macro != 0)
                        mid = s->s_macro;
                else
                {
                        if (NextMacroId > MAXMACROID)
                        {
                                syserr("Macro/class {%s}: too many long names",
                                        mbuf);
                                s->s_macro = -1;
                        }
                        else
                        {
                                MacroName[NextMacroId] = s->s_name;
                                s->s_macro = mid = NextMacroId;
                                NextMacroId = NEXTMACROID(NextMacroId);
                        }
                }
                p++;
        }
        if (ep != NULL)
                *ep = p;
        if (mid < 0 || mid > MAXMACROID)
        {
                syserr("Unable to assign macro/class ID (mid = 0x%x)", mid);
                if (tTd(35, 14))
                        sm_dprintf("NULL\n");
                return 0;
        }
        if (tTd(35, 14))
                sm_dprintf("0x%x\n", mid);
        return mid;
}

/*
**  WORDINCLASS -- tell if a word is in a specific class
**
**      Parameters:
**              str -- the name of the word to look up.
**              cl -- the class name.
**
**      Returns:
**              true if str can be found in cl.
**              false otherwise.
*/

bool
wordinclass(str, cl)
        char *str;
        int cl;
{
        STAB *s;

        s = stab(str, ST_CLASS, ST_FIND);
        return s != NULL && bitnset(bitidx(cl), s->s_class);
}