/* * 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 SM_RCSID("@(#)$Id: macro.c,v 8.107 2007/08/06 22:29:02 ca Exp $") #include #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); }