1/*++ 2/* NAME 3/* mac_expand 3 4/* SUMMARY 5/* attribute expansion 6/* SYNOPSIS 7/* #include <mac_expand.h> 8/* 9/* int mac_expand(result, pattern, flags, filter, lookup, context) 10/* VSTRING *result; 11/* const char *pattern; 12/* int flags; 13/* const char *filter; 14/* const char *lookup(const char *key, int mode, char *context) 15/* char *context; 16/* DESCRIPTION 17/* This module implements parameter-less macro expansions, both 18/* conditional and unconditional, and both recursive and non-recursive. 19/* 20/* In this text, an attribute is considered "undefined" when its value 21/* is a null pointer. Otherwise, the attribute is considered "defined" 22/* and is expected to have as value a null-terminated string. 23/* 24/* The following expansions are implemented: 25/* .IP "$name, ${name}, $(name)" 26/* Unconditional expansion. If the named attribute value is non-empty, the 27/* expansion is the value of the named attribute, optionally subjected 28/* to further $name expansions. Otherwise, the expansion is empty. 29/* .IP "${name?text}, $(name?text)" 30/* Conditional expansion. If the named attribute value is non-empty, the 31/* expansion is the given text, subjected to another iteration of 32/* $name expansion. Otherwise, the expansion is empty. 33/* .IP "${name:text}, $(name:text)" 34/* Conditional expansion. If the attribute value is empty or undefined, 35/* the expansion is the given text, subjected to another iteration 36/* of $name expansion. Otherwise, the expansion is empty. 37/* .PP 38/* Arguments: 39/* .IP result 40/* Storage for the result of expansion. The result is truncated 41/* upon entry. 42/* .IP pattern 43/* The string to be expanded. 44/* .IP flags 45/* Bit-wise OR of zero or more of the following: 46/* .RS 47/* .IP MAC_EXP_FLAG_RECURSE 48/* Expand macros in lookup results. This should never be done with 49/* data whose origin is untrusted. 50/* .IP MAC_EXP_FLAG_APPEND 51/* Append text to the result buffer without truncating it. 52/* .IP MAC_EXP_FLAG_SCAN 53/* Invoke the call-back function each macro name in the input 54/* string, including macro names in the values of conditional 55/* expressions. Do not expand macros, and do not write to the 56/* result argument. 57/* .PP 58/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value. 59/* .RE 60/* .IP filter 61/* A null pointer, or a null-terminated array of characters that 62/* are allowed to appear in an expansion. Illegal characters are 63/* replaced by underscores. 64/* .IP lookup 65/* The attribute lookup routine. Arguments are: the attribute name, 66/* MAC_EXP_MODE_TEST to test the existence of the named attribute 67/* or MAC_EXP_MODE_USE to use the value of the named attribute, 68/* and the caller context that was given to mac_expand(). A null 69/* result value means that the requested attribute was not defined. 70/* .IP context 71/* Caller context that is passed on to the attribute lookup routine. 72/* DIAGNOSTICS 73/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable 74/* macro nesting. 75/* 76/* The result value is the binary OR of zero or more of the following: 77/* .IP MAC_PARSE_ERROR 78/* A syntax error was found in \fBpattern\fR, or some macro had 79/* an unreasonable nesting depth. 80/* .IP MAC_PARSE_UNDEF 81/* A macro was expanded but its value not defined. 82/* SEE ALSO 83/* mac_parse(3) locate macro references in string. 84/* LICENSE 85/* .ad 86/* .fi 87/* The Secure Mailer license must be distributed with this software. 88/* AUTHOR(S) 89/* Wietse Venema 90/* IBM T.J. Watson Research 91/* P.O. Box 704 92/* Yorktown Heights, NY 10598, USA 93/*--*/ 94 95/* System library. */ 96 97#include <sys_defs.h> 98#include <ctype.h> 99#include <string.h> 100 101/* Utility library. */ 102 103#include <msg.h> 104#include <vstring.h> 105#include <mymalloc.h> 106#include <mac_parse.h> 107#include <mac_expand.h> 108 109 /* 110 * Little helper structure. 111 */ 112typedef struct { 113 VSTRING *result; /* result buffer */ 114 int flags; /* features */ 115 const char *filter; /* character filter */ 116 MAC_EXP_LOOKUP_FN lookup; /* lookup routine */ 117 char *context; /* caller context */ 118 int status; /* findings */ 119 int level; /* nesting level */ 120} MAC_EXP; 121 122/* mac_expand_callback - callback for mac_parse */ 123 124static int mac_expand_callback(int type, VSTRING *buf, char *ptr) 125{ 126 MAC_EXP *mc = (MAC_EXP *) ptr; 127 int lookup_mode; 128 const char *text; 129 char *cp; 130 int ch; 131 ssize_t len; 132 133 /* 134 * Sanity check. 135 */ 136 if (mc->level++ > 100) { 137 msg_warn("unreasonable macro call nesting: \"%s\"", vstring_str(buf)); 138 mc->status |= MAC_PARSE_ERROR; 139 } 140 if (mc->status & MAC_PARSE_ERROR) 141 return (mc->status); 142 143 /* 144 * $Name etc. reference. 145 * 146 * In order to support expansion of lookup results, we must save the lookup 147 * result. We use the input buffer since it will not be needed anymore. 148 */ 149 if (type == MAC_PARSE_EXPR) { 150 151 /* 152 * Look for the ? or : delimiter. In case of a syntax error, return 153 * without doing damage, and issue a warning instead. 154 */ 155 for (cp = vstring_str(buf); /* void */ ; cp++) { 156 if ((ch = *cp) == 0) { 157 lookup_mode = MAC_EXP_MODE_USE; 158 break; 159 } 160 if (ch == '?' || ch == ':') { 161 *cp++ = 0; 162 lookup_mode = MAC_EXP_MODE_TEST; 163 break; 164 } 165 if (!ISALNUM(ch) && ch != '_') { 166 msg_warn("macro name syntax error: \"%s\"", vstring_str(buf)); 167 mc->status |= MAC_PARSE_ERROR; 168 return (mc->status); 169 } 170 } 171 172 /* 173 * Look up the named parameter. 174 */ 175 text = mc->lookup(vstring_str(buf), lookup_mode, mc->context); 176 177 /* 178 * Perform the requested substitution. 179 */ 180 switch (ch) { 181 case '?': 182 if ((text != 0 && *text != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) 183 mac_parse(cp, mac_expand_callback, (char *) mc); 184 break; 185 case ':': 186 if (text == 0 || *text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) 187 mac_parse(cp, mac_expand_callback, (char *) mc); 188 break; 189 default: 190 if (text == 0) { 191 mc->status |= MAC_PARSE_UNDEF; 192 } else if (*text == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { 193 /* void */ ; 194 } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { 195 vstring_strcpy(buf, text); 196 mac_parse(vstring_str(buf), mac_expand_callback, (char *) mc); 197 } else { 198 len = VSTRING_LEN(mc->result); 199 vstring_strcat(mc->result, text); 200 if (mc->filter) { 201 cp = vstring_str(mc->result) + len; 202 while (*(cp += strspn(cp, mc->filter))) 203 *cp++ = '_'; 204 } 205 } 206 break; 207 } 208 } 209 210 /* 211 * Literal text. 212 */ 213 else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { 214 vstring_strcat(mc->result, vstring_str(buf)); 215 } 216 217 mc->level--; 218 219 return (mc->status); 220} 221 222/* mac_expand - expand $name instances */ 223 224int mac_expand(VSTRING *result, const char *pattern, int flags, 225 const char *filter, 226 MAC_EXP_LOOKUP_FN lookup, char *context) 227{ 228 MAC_EXP mc; 229 int status; 230 231 /* 232 * Bundle up the request and do the substitutions. 233 */ 234 mc.result = result; 235 mc.flags = flags; 236 mc.filter = filter; 237 mc.lookup = lookup; 238 mc.context = context; 239 mc.status = 0; 240 mc.level = 0; 241 if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0) 242 VSTRING_RESET(result); 243 status = mac_parse(pattern, mac_expand_callback, (char *) &mc); 244 if ((flags & MAC_EXP_FLAG_SCAN) == 0) 245 VSTRING_TERMINATE(result); 246 247 return (status); 248} 249 250#ifdef TEST 251 252 /* 253 * This code certainly deserves a stand-alone test program. 254 */ 255#include <stdlib.h> 256#include <stringops.h> 257#include <htable.h> 258#include <vstream.h> 259#include <vstring_vstream.h> 260 261static const char *lookup(const char *name, int unused_mode, char *context) 262{ 263 HTABLE *table = (HTABLE *) context; 264 265 return (htable_find(table, name)); 266} 267 268int main(int unused_argc, char **unused_argv) 269{ 270 VSTRING *buf = vstring_alloc(100); 271 VSTRING *result = vstring_alloc(100); 272 char *cp; 273 char *name; 274 char *value; 275 HTABLE *table; 276 int stat; 277 278 while (!vstream_feof(VSTREAM_IN)) { 279 280 table = htable_create(0); 281 282 /* 283 * Read a block of definitions, terminated with an empty line. 284 */ 285 while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { 286 vstream_printf("<< %s\n", vstring_str(buf)); 287 vstream_fflush(VSTREAM_OUT); 288 if (VSTRING_LEN(buf) == 0) 289 break; 290 cp = vstring_str(buf); 291 name = mystrtok(&cp, " \t\r\n="); 292 value = mystrtok(&cp, " \t\r\n="); 293 htable_enter(table, name, value ? mystrdup(value) : 0); 294 } 295 296 /* 297 * Read a block of patterns, terminated with an empty line or EOF. 298 */ 299 while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { 300 vstream_printf("<< %s\n", vstring_str(buf)); 301 vstream_fflush(VSTREAM_OUT); 302 if (VSTRING_LEN(buf) == 0) 303 break; 304 cp = vstring_str(buf); 305 VSTRING_RESET(result); 306 stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, 307 (char *) 0, lookup, (char *) table); 308 vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); 309 vstream_fflush(VSTREAM_OUT); 310 } 311 htable_free(table, myfree); 312 vstream_printf("\n"); 313 } 314 315 /* 316 * Clean up. 317 */ 318 vstring_free(buf); 319 vstring_free(result); 320 exit(0); 321} 322 323#endif 324