Coverage Report

Created: 2024-02-11 06:14

/src/postfix/postfix/src/util/mac_expand.c
Line
Count
Source (jump to first uncovered line)
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, void *context)
15
/*  void *context;
16
/* AUXILIARY FUNCTIONS
17
/*  typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (
18
/*  const char *left,
19
/*  int tok_val,
20
/*  const char *rite)
21
/*
22
/*  void  mac_expand_add_relop(
23
/*  int *tok_list,
24
/*  const char *suffix,
25
/*  MAC_EXPAND_RELOP_FN relop_eval)
26
/*
27
/*  MAC_EXP_OP_RES mac_exp_op_res_bool[2];
28
/* DESCRIPTION
29
/*  This module implements parameter-less named attribute
30
/*  expansions, both conditional and unconditional. As of Postfix
31
/*  3.0 this code supports relational expression evaluation.
32
/*
33
/*  In this text, an attribute is considered "undefined" when its value
34
/*  is a null pointer.  Otherwise, the attribute is considered "defined"
35
/*  and is expected to have as value a null-terminated string.
36
/*
37
/*  In the text below, the legacy form $(...) is equivalent to
38
/*  ${...}. The legacy form $(...) may eventually disappear
39
/*  from documentation. In the text below, the name in $name
40
/*  and ${name...} must contain only characters from the set
41
/*  [a-zA-Z0-9_].
42
/*
43
/*  The following substitutions are supported:
44
/* .IP "$name, ${name}"
45
/*  Unconditional attribute-based substitution. The result is the
46
/*  named attribute value (empty if the attribute is not defined)
47
/*  after optional further named attribute substitution.
48
/* .IP "${name?text}, ${name?{text}}"
49
/*  Conditional attribute-based substitution. If the named attribute
50
/*  value is non-empty, the result is the given text, after
51
/*  named attribute expansion and relational expression evaluation.
52
/*  Otherwise, the result is empty.  Whitespace before or after
53
/*  {text} is ignored.
54
/* .IP "${name:text}, ${name:{text}}"
55
/*  Conditional attribute-based substitution. If the attribute
56
/*  value is empty or undefined, the expansion is the given
57
/*  text, after named attribute expansion and relational expression
58
/*  evaluation.  Otherwise, the result is empty.  Whitespace
59
/*  before or after {text} is ignored.
60
/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}"
61
/*  Conditional attribute-based substitution. If the named attribute
62
/*  value is non-empty, the result is text1.  Otherwise, the
63
/*  result is text2. In both cases the result is subject to
64
/*  named attribute expansion and relational expression evaluation.
65
/*  Whitespace before or after {text1} or {text2} is ignored.
66
/* .IP "${{text1} == ${text2} ? {text3} : {text4}}"
67
/*  Relational expression-based substitution.  First, the content
68
/*  of {text1} and ${text2} is subjected to named attribute and
69
/*  relational expression-based substitution.  Next, the relational
70
/*  expression is evaluated. If it evaluates to "true", the
71
/*  result is the content of {text3}, otherwise it is the content
72
/*  of {text4}, after named attribute and relational expression-based
73
/*  substitution. In addition to ==, this supports !=, <, <=,
74
/*  >=, and >. Comparisons are numerical when both operands are
75
/*  all digits, otherwise the comparisons are lexicographical.
76
/*
77
/*  Arguments:
78
/* .IP result
79
/*  Storage for the result of expansion. By default, the result
80
/*  is truncated upon entry.
81
/* .IP pattern
82
/*  The string to be expanded.
83
/* .IP flags
84
/*  Bit-wise OR of zero or more of the following:
85
/* .RS
86
/* .IP MAC_EXP_FLAG_RECURSE
87
/*  Expand attributes in lookup results. This should never be
88
/*  done with data whose origin is untrusted.
89
/* .IP MAC_EXP_FLAG_APPEND
90
/*  Append text to the result buffer without truncating it.
91
/* .IP MAC_EXP_FLAG_SCAN
92
/*  Scan the input for named attributes, including named
93
/*  attributes in all conditional result values.  Do not expand
94
/*  named attributes, and do not truncate or write to the result
95
/*  argument.
96
/* .IP MAC_EXP_FLAG_PRINTABLE
97
/*  Use the printable() function instead of \fIfilter\fR.
98
/* .PP
99
/*  The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
100
/* .RE
101
/* .IP filter
102
/*  A null pointer, or a null-terminated array of characters that
103
/*  are allowed to appear in an expansion. Illegal characters are
104
/*  replaced by underscores.
105
/* .IP lookup
106
/*  The attribute lookup routine. Arguments are: the attribute name,
107
/*  MAC_EXP_MODE_TEST to test the existence of the named attribute
108
/*  or MAC_EXP_MODE_USE to use the value of the named attribute,
109
/*  and the caller context that was given to mac_expand(). A null
110
/*  result value means that the requested attribute was not defined.
111
/* .IP context
112
/*  Caller context that is passed on to the attribute lookup routine.
113
/* .PP
114
/*  mac_expand_add_relop() registers a function that implements
115
/*  support for custom relational operators. Custom operator names
116
/*  such as "==xxx" have two parts: a prefix that is identical to
117
/*  a built-in operator such as "==", and an application-specified
118
/*  suffix such as "xxx".
119
/*
120
/*  Arguments:
121
/* .IP tok_list
122
/*  A null-terminated list of MAC_EXP_OP_TOK_* values that support
123
/*  the custom operator suffix.
124
/* .IP suffix
125
/*  A null-terminated alphanumeric string that specifies the custom
126
/*  operator suffix.
127
/* .IP relop_eval
128
/*  A function that compares two strings according to the
129
/*  MAC_EXP_OP_TOK_* value specified with the tok_val argument,
130
/*  and that returns non-zero if the custom operator evaluates to
131
/*  true, zero otherwise.
132
/*
133
/*  mac_exp_op_res_bool provides an array that converts a boolean
134
/*  value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or
135
/*  MAX_EXP_OP_RES_FALSE value.
136
/* DIAGNOSTICS
137
/*  Fatal errors: out of memory.  Warnings: syntax errors, unreasonable
138
/*  recursion depth.
139
/*
140
/*  The result value is the binary OR of zero or more of the following:
141
/* .IP MAC_PARSE_ERROR
142
/*  A syntax error was found in \fBpattern\fR, or some attribute had
143
/*  an unreasonable nesting depth.
144
/* .IP MAC_PARSE_UNDEF
145
/*  An attribute was expanded but its value was not defined.
146
/* SEE ALSO
147
/*  mac_parse(3) locate macro references in string.
148
/* LICENSE
149
/* .ad
150
/* .fi
151
/*  The Secure Mailer license must be distributed with this software.
152
/* AUTHOR(S)
153
/*  Wietse Venema
154
/*  IBM T.J. Watson Research
155
/*  P.O. Box 704
156
/*  Yorktown Heights, NY 10598, USA
157
/*
158
/*  Wietse Venema
159
/*  Google, Inc.
160
/*  111 8th Avenue
161
/*  New York, NY 10011, USA
162
/*--*/
163
164
/* System library. */
165
166
#include <sys_defs.h>
167
#include <ctype.h>
168
#include <errno.h>
169
#include <string.h>
170
#include <stdlib.h>
171
172
/* Utility library. */
173
174
#include <msg.h>
175
#include <htable.h>
176
#include <vstring.h>
177
#include <mymalloc.h>
178
#include <stringops.h>
179
#include <name_code.h>
180
#include <sane_strtol.h>
181
#include <mac_parse.h>
182
#include <mac_expand.h>
183
184
 /*
185
  * Simplifies the return of common relational operator results.
186
  */
187
MAC_EXP_OP_RES mac_exp_op_res_bool[2] = {
188
    MAC_EXP_OP_RES_FALSE,
189
    MAC_EXP_OP_RES_TRUE
190
};
191
192
 /*
193
  * Little helper structure.
194
  */
195
typedef struct {
196
    VSTRING *result;      /* result buffer */
197
    int     flags;      /* features */
198
    const char *filter;     /* character filter */
199
    MAC_EXP_LOOKUP_FN lookup;   /* lookup routine */
200
    void   *context;      /* caller context */
201
    int     status;     /* findings */
202
    int     level;      /* nesting level */
203
} MAC_EXP_CONTEXT;
204
205
 /*
206
  * Support for relational expressions.
207
  * 
208
  * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the
209
  * result respectively when the parameter value is non-empty, or when the
210
  * parameter value is undefined or empty; support for the ternary ?:
211
  * operator was anticipated, but not implemented for 10 years.
212
  * 
213
  * To make ${relational-expr?result} and ${relational-expr:result} work as
214
  * expected without breaking the way that ? and : work, relational
215
  * expressions evaluate to a non-empty or empty value. It does not matter
216
  * what non-empty value we use for TRUE. However we must not use the
217
  * undefined (null pointer) value for FALSE - that would raise the
218
  * MAC_PARSE_UNDEF flag.
219
  * 
220
  * The value of a relational expression can be exposed with ${relational-expr},
221
  * i.e. a relational expression that is not followed by ? or : conditional
222
  * expansion.
223
  */
224
0
#define MAC_EXP_BVAL_TRUE "true"
225
0
#define MAC_EXP_BVAL_FALSE  ""
226
227
 /*
228
  * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header
229
  * file.
230
  */
231
#define MAC_EXP_OP_STR_EQ "=="
232
#define MAC_EXP_OP_STR_NE "!="
233
#define MAC_EXP_OP_STR_LT "<"
234
#define MAC_EXP_OP_STR_LE "<="
235
#define MAC_EXP_OP_STR_GE ">="
236
#define MAC_EXP_OP_STR_GT ">"
237
#define MAC_EXP_OP_STR_ANY  "\"" MAC_EXP_OP_STR_EQ \
238
        "\" or \"" MAC_EXP_OP_STR_NE "\"" \
239
        "\" or \"" MAC_EXP_OP_STR_LT "\"" \
240
        "\" or \"" MAC_EXP_OP_STR_LE "\"" \
241
        "\" or \"" MAC_EXP_OP_STR_GE "\"" \
242
        "\" or \"" MAC_EXP_OP_STR_GT "\""
243
244
static const NAME_CODE mac_exp_op_table[] =
245
{
246
    MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
247
    MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE,
248
    MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT,
249
    MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE,
250
    MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE,
251
    MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT,
252
    0, MAC_EXP_OP_TOK_NONE,
253
};
254
255
 /*
256
  * The whitespace separator set.
257
  */
258
0
#define MAC_EXP_WHITESPACE  CHARS_SPACE
259
260
 /*
261
  * Support for operator extensions.
262
  */
263
static HTABLE *mac_exp_ext_table;
264
static VSTRING *mac_exp_ext_key;
265
266
 /*
267
  * SLMs.
268
  */
269
0
#define STR(x)  vstring_str(x)
270
271
/* atol_or_die - convert or die */
272
273
static long atol_or_die(const char *strval)
274
0
{
275
0
    long    result;
276
0
    char   *remainder;
277
278
0
    result = sane_strtol(strval, &remainder, 10);
279
0
    if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE)
280
0
  msg_fatal("mac_exp_eval: bad conversion: %s", strval);
281
0
    return (result);
282
0
}
283
284
/* mac_exp_eval - evaluate binary expression */
285
286
static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val,
287
                   const char *rite)
288
0
{
289
0
    static const char myname[] = "mac_exp_eval";
290
0
    long    delta;
291
292
    /*
293
     * Numerical or string comparison.
294
     */
295
0
    if (alldig(left) && alldig(rite)) {
296
0
  delta = atol_or_die(left) - atol_or_die(rite);
297
0
    } else {
298
0
  delta = strcmp(left, rite);
299
0
    }
300
0
    switch (tok_val) {
301
0
    case MAC_EXP_OP_TOK_EQ:
302
0
  return (mac_exp_op_res_bool[delta == 0]);
303
0
    case MAC_EXP_OP_TOK_NE:
304
0
  return (mac_exp_op_res_bool[delta != 0]);
305
0
    case MAC_EXP_OP_TOK_LT:
306
0
  return (mac_exp_op_res_bool[delta < 0]);
307
0
    case MAC_EXP_OP_TOK_LE:
308
0
  return (mac_exp_op_res_bool[delta <= 0]);
309
0
    case MAC_EXP_OP_TOK_GE:
310
0
  return (mac_exp_op_res_bool[delta >= 0]);
311
0
    case MAC_EXP_OP_TOK_GT:
312
0
  return (mac_exp_op_res_bool[delta > 0]);
313
0
    default:
314
0
  msg_panic("%s: unknown operator: %d",
315
0
      myname, tok_val);
316
0
    }
317
0
}
318
319
/* mac_exp_parse_error - report parse error, set error flag, return status */
320
321
static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc,
322
                    const char *fmt,...)
323
0
{
324
0
    va_list ap;
325
326
0
    va_start(ap, fmt);
327
0
    vmsg_warn(fmt, ap);
328
0
    va_end(ap);
329
0
    return (mc->status |= MAC_PARSE_ERROR);
330
0
};
331
332
/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */
333
334
0
#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \
335
0
  return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \
336
0
    } while (0)
337
338
 /*
339
  * Postfix 3.0 introduces support for {text} operands. Only with these do we
340
  * support the ternary ?: operator and relational operators.
341
  * 
342
  * We cannot support operators in random text, because that would break Postfix
343
  * 2.11 compatibility. For example, with the expression "${name?value}", the
344
  * value is random text that may contain ':', '?', '{' and '}' characters.
345
  * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates
346
  * to "?foo:{b}ar" or empty. There are explicit tests in this directory and
347
  * the postconf directory to ensure that Postfix 2.11 compatibility is
348
  * maintained.
349
  * 
350
  * Ideally, future Postfix configurations enclose random text operands inside
351
  * {} braces. These allow whitespace around operands, which improves
352
  * readability.
353
  */
354
355
/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */
356
357
#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \
358
0
  ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \
359
0
   (cp += len) : 0)
360
361
/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */
362
363
static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp)
364
0
{
365
0
    char   *payload;
366
0
    char   *cp;
367
0
    int     level;
368
0
    int     ch;
369
370
    /*
371
     * Extract the payload and balance the {}. The caller is expected to skip
372
     * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY().
373
     */
374
0
    for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) {
375
0
  if ((ch = *cp) == 0) {
376
0
      mac_exp_parse_error(mc, "unbalanced {} in attribute expression: "
377
0
        "\"%s\"",
378
0
        *bp);
379
0
      return (0);
380
0
  } else if (ch == '{') {
381
0
      level++;
382
0
  } else if (ch == '}') {
383
0
      if (--level <= 0)
384
0
    break;
385
0
  }
386
0
    }
387
0
    *cp++ = 0;
388
389
    /*
390
     * Skip trailing whitespace after }.
391
     */
392
0
    *bp = cp + strspn(cp, MAC_EXP_WHITESPACE);
393
0
    return (payload);
394
0
}
395
396
/* mac_exp_parse_relational - parse relational expression, advance read ptr */
397
398
static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup,
399
                    char **bp)
400
0
{
401
0
    char   *cp = *bp;
402
0
    VSTRING *left_op_buf;
403
0
    VSTRING *rite_op_buf;
404
0
    const char *left_op_strval;
405
0
    const char *rite_op_strval;
406
0
    char   *op_pos;
407
0
    char   *op_strval;
408
0
    size_t  op_len;
409
0
    int     op_tokval;
410
0
    int     op_result;
411
0
    size_t  tmp_len;
412
0
    char   *type_pos;
413
0
    size_t  type_len;
414
0
    MAC_EXPAND_RELOP_FN relop_eval;
415
416
    /*
417
     * Left operand. The caller is expected to skip leading whitespace before
418
     * the {. See MAC_EXP_FIND_LEFT_CURLY().
419
     */
420
0
    if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
421
0
  return (mc->status);
422
423
    /*
424
     * Operator. Todo: regexp operator.
425
     */
426
0
    op_pos = cp;
427
0
    op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */
428
0
    op_strval = mystrndup(cp, op_len);
429
0
    op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval);
430
0
    myfree(op_strval);
431
0
    if (op_tokval == MAC_EXP_OP_TOK_NONE)
432
0
  MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"",
433
0
         MAC_EXP_OP_STR_ANY, left_op_strval, cp);
434
0
    cp += op_len;
435
436
    /*
437
     * Custom operator suffix.
438
     */
439
0
    if (mac_exp_ext_table && ISALNUM(*cp)) {
440
0
  type_pos = cp;
441
0
  for (type_len = 1; ISALNUM(cp[type_len]); type_len++)
442
0
       /* void */ ;
443
0
  cp += type_len;
444
0
  vstring_sprintf(mac_exp_ext_key, "%.*s",
445
0
      (int) (op_len + type_len), op_pos);
446
0
  if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table,
447
0
            STR(mac_exp_ext_key))) == 0)
448
0
      MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"",
449
0
          (int) op_len, op_pos, (int) type_len, type_pos);
450
0
    } else {
451
0
  relop_eval = mac_exp_eval;
452
0
    }
453
454
    /*
455
     * Right operand. Todo: syntax may depend on operator.
456
     */
457
0
    if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0)
458
0
  MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: "
459
0
         "\"...{%s} %.*s>>>%.20s\"",
460
0
         left_op_strval, (int) op_len, op_pos, cp);
461
0
    if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
462
0
  return (mc->status);
463
464
    /*
465
     * Evaluate the relational expression. Todo: regexp support.
466
     */
467
0
    mc->status |=
468
0
  mac_expand(left_op_buf = vstring_alloc(100), left_op_strval,
469
0
       mc->flags, mc->filter, mc->lookup, mc->context);
470
0
    mc->status |=
471
0
  mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
472
0
       mc->flags, mc->filter, mc->lookup, mc->context);
473
0
    if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0
474
0
  && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval,
475
0
       vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR)
476
0
  mc->status |= MAC_PARSE_ERROR;
477
0
    vstring_free(left_op_buf);
478
0
    vstring_free(rite_op_buf);
479
0
    if (mc->status & MAC_PARSE_ERROR)
480
0
  return (mc->status);
481
482
    /*
483
     * Here, we fake up a non-empty or empty parameter value lookup result,
484
     * for compatibility with the historical code that looks named parameter
485
     * values.
486
     */
487
0
    if (mc->flags & MAC_EXP_FLAG_SCAN) {
488
0
  *lookup = 0;
489
0
    } else {
490
0
  switch (op_result) {
491
0
  case MAC_EXP_OP_RES_TRUE:
492
0
      *lookup = MAC_EXP_BVAL_TRUE;
493
0
      break;
494
0
  case MAC_EXP_OP_RES_FALSE:
495
0
      *lookup = MAC_EXP_BVAL_FALSE;
496
0
      break;
497
0
  default:
498
0
      msg_panic("mac_expand: unexpected operator result: %d", op_result);
499
0
  }
500
0
    }
501
0
    *bp = cp;
502
0
    return (0);
503
0
}
504
505
/* mac_expand_add_relop - register operator extensions */
506
507
void    mac_expand_add_relop(int *tok_list, const char *suffix,
508
                   MAC_EXPAND_RELOP_FN relop_eval)
509
0
{
510
0
    const char myname[] = "mac_expand_add_relop";
511
0
    const char *tok_name;
512
0
    int    *tp;
513
514
    /*
515
     * Sanity checks.
516
     */
517
0
    if (!allalnum(suffix))
518
0
  msg_panic("%s: bad operator suffix: %s", myname, suffix);
519
520
    /*
521
     * One-time initialization.
522
     */
523
0
    if (mac_exp_ext_table == 0) {
524
0
  mac_exp_ext_table = htable_create(10);
525
0
  mac_exp_ext_key = vstring_alloc(10);
526
0
    }
527
0
    for (tp = tok_list; *tp; tp++) {
528
0
  if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0)
529
0
      msg_panic("%s: unknown token code: %d", myname, *tp);
530
0
  vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix);
531
0
  if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0)
532
0
      msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key));
533
0
  (void) htable_enter(mac_exp_ext_table,
534
0
          STR(mac_exp_ext_key), (void *) relop_eval);
535
0
    }
536
0
}
537
538
/* mac_expand_callback - callback for mac_parse */
539
540
static int mac_expand_callback(int type, VSTRING *buf, void *ptr)
541
0
{
542
0
    static const char myname[] = "mac_expand_callback";
543
0
    MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr;
544
0
    int     lookup_mode;
545
0
    const char *lookup;
546
0
    char   *cp;
547
0
    int     ch;
548
0
    ssize_t res_len;
549
0
    ssize_t tmp_len;
550
0
    const char *res_iftrue;
551
0
    const char *res_iffalse;
552
553
    /*
554
     * Sanity check.
555
     */
556
0
    if (mc->level++ > 100)
557
0
  mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"",
558
0
          vstring_str(buf));
559
0
    if (mc->status & MAC_PARSE_ERROR)
560
0
  return (mc->status);
561
562
    /*
563
     * Named parameter or relational expression. In case of a syntax error,
564
     * return without doing damage, and issue a warning instead.
565
     */
566
0
    if (type == MAC_PARSE_EXPR) {
567
568
0
  cp = vstring_str(buf);
569
570
  /*
571
   * Relational expression. If recursion is disabled, perform only one
572
   * level of $name expansion.
573
   */
574
0
  if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
575
0
      if (mac_exp_parse_relational(mc, &lookup, &cp) != 0)
576
0
    return (mc->status);
577
578
      /*
579
       * Look for the ? or : operator.
580
       */
581
0
      if ((ch = *cp) != 0) {
582
0
    if (ch != '?' && ch != ':')
583
0
        MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: "
584
0
               "\"...}>>>%.20s\"", cp);
585
0
    cp++;
586
0
      }
587
0
  }
588
589
  /*
590
   * Named parameter.
591
   */
592
0
  else {
593
0
      char   *start;
594
595
      /*
596
       * Look for the ? or : operator. In case of a syntax error,
597
       * return without doing damage, and issue a warning instead.
598
       */
599
0
      start = (cp += strspn(cp, MAC_EXP_WHITESPACE));
600
0
      for ( /* void */ ; /* void */ ; cp++) {
601
0
    if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) {
602
0
        *cp = 0;
603
0
        lookup_mode = MAC_EXP_MODE_USE;
604
0
        break;
605
0
    }
606
0
    if (ch == '?' || ch == ':') {
607
0
        *cp++ = 0;
608
0
        cp += tmp_len;
609
0
        lookup_mode = MAC_EXP_MODE_TEST;
610
0
        break;
611
0
    }
612
0
    ch = *cp;
613
0
    if (!ISALNUM(ch) && ch != '_') {
614
0
        MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: "
615
0
               "\"...%.*s>>>%.20s\"",
616
0
               (int) (cp - vstring_str(buf)),
617
0
               vstring_str(buf), cp);
618
0
    }
619
0
      }
620
621
      /*
622
       * Look up the named parameter. Todo: allow the lookup function
623
       * to specify if the result is safe for $name expansion.
624
       */
625
0
      lookup = mc->lookup(start, lookup_mode, mc->context);
626
0
  }
627
628
  /*
629
   * Return the requested result. After parsing the result operand
630
   * following ?, we fall through to parse the result operand following
631
   * :. This is necessary with the ternary ?: operator: first, with
632
   * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(),
633
   * and second, to find garbage after any result operand. Without
634
   * MAC_EXP_FLAG_SCAN the content of only one of the ?: result
635
   * operands will be parsed with mac_parse(); syntax errors in the
636
   * other operand will be missed.
637
   */
638
0
  switch (ch) {
639
0
  case '?':
640
0
      if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
641
0
    if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0)
642
0
        return (mc->status);
643
0
      } else {
644
0
    res_iftrue = cp;
645
0
    cp = "";      /* no left-over text */
646
0
      }
647
0
      if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
648
0
    mc->status |= mac_parse(res_iftrue, mac_expand_callback,
649
0
          (void *) mc);
650
0
      if (*cp == 0)     /* end of input, OK */
651
0
    break;
652
0
      if (*cp != ':')     /* garbage */
653
0
    MAC_EXP_ERR_RETURN(mc, "\":\" expected at: "
654
0
           "\"...%s}>>>%.20s\"", res_iftrue, cp);
655
0
      cp += 1;
656
      /* FALLTHROUGH: do not remove, see comment above. */
657
0
  case ':':
658
0
      if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
659
0
    if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0)
660
0
        return (mc->status);
661
0
      } else {
662
0
    res_iffalse = cp;
663
0
    cp = "";      /* no left-over text */
664
0
      }
665
0
      if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
666
0
    mc->status |= mac_parse(res_iffalse, mac_expand_callback,
667
0
          (void *) mc);
668
0
      if (*cp != 0)     /* garbage */
669
0
    MAC_EXP_ERR_RETURN(mc, "unexpected input at: "
670
0
           "\"...%s}>>>%.20s\"", res_iffalse, cp);
671
0
      break;
672
0
  case 0:
673
0
      if (lookup == 0) {
674
0
    mc->status |= MAC_PARSE_UNDEF;
675
0
      } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
676
0
     /* void */ ;
677
0
      } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
678
0
    vstring_strcpy(buf, lookup);
679
0
    mc->status |= mac_parse(vstring_str(buf), mac_expand_callback,
680
0
          (void *) mc);
681
0
      } else {
682
0
    res_len = VSTRING_LEN(mc->result);
683
0
    vstring_strcat(mc->result, lookup);
684
0
    if (mc->flags & MAC_EXP_FLAG_PRINTABLE) {
685
0
        printable(vstring_str(mc->result) + res_len, '_');
686
0
    } else if (mc->filter) {
687
0
        cp = vstring_str(mc->result) + res_len;
688
0
        while (*(cp += strspn(cp, mc->filter)))
689
0
      *cp++ = '_';
690
0
    }
691
0
      }
692
0
      break;
693
0
  default:
694
0
      msg_panic("%s: unknown operator code %d", myname, ch);
695
0
  }
696
0
    }
697
698
    /*
699
     * Literal text.
700
     */
701
0
    else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) {
702
0
  vstring_strcat(mc->result, vstring_str(buf));
703
0
    }
704
0
    mc->level--;
705
706
0
    return (mc->status);
707
0
}
708
709
/* mac_expand - expand $name instances */
710
711
int     mac_expand(VSTRING *result, const char *pattern, int flags,
712
               const char *filter,
713
               MAC_EXP_LOOKUP_FN lookup, void *context)
714
0
{
715
0
    MAC_EXP_CONTEXT mc;
716
0
    int     status;
717
718
    /*
719
     * Bundle up the request and do the substitutions.
720
     */
721
0
    mc.result = result;
722
0
    mc.flags = flags;
723
0
    mc.filter = filter;
724
0
    mc.lookup = lookup;
725
0
    mc.context = context;
726
0
    mc.status = 0;
727
0
    mc.level = 0;
728
0
    if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0)
729
0
  VSTRING_RESET(result);
730
0
    status = mac_parse(pattern, mac_expand_callback, (void *) &mc);
731
0
    if ((flags & MAC_EXP_FLAG_SCAN) == 0)
732
0
  VSTRING_TERMINATE(result);
733
734
0
    return (status);
735
0
}
736
737
#ifdef TEST
738
739
 /*
740
  * This code certainly deserves a stand-alone test program.
741
  */
742
#include <stringops.h>
743
#include <htable.h>
744
#include <vstream.h>
745
#include <vstring_vstream.h>
746
747
static const char *lookup(const char *name, int unused_mode, void *context)
748
{
749
    HTABLE *table = (HTABLE *) context;
750
751
    return (htable_find(table, name));
752
}
753
754
static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop,
755
                  const char *rite)
756
{
757
    const char myname[] = "length_relop_eval";
758
    ssize_t delta = strlen(left) - strlen(rite);
759
760
    switch (relop) {
761
    case MAC_EXP_OP_TOK_EQ:
762
  return (mac_exp_op_res_bool[delta == 0]);
763
    case MAC_EXP_OP_TOK_NE:
764
  return (mac_exp_op_res_bool[delta != 0]);
765
    case MAC_EXP_OP_TOK_LT:
766
  return (mac_exp_op_res_bool[delta < 0]);
767
    case MAC_EXP_OP_TOK_LE:
768
  return (mac_exp_op_res_bool[delta <= 0]);
769
    case MAC_EXP_OP_TOK_GE:
770
  return (mac_exp_op_res_bool[delta >= 0]);
771
    case MAC_EXP_OP_TOK_GT:
772
  return (mac_exp_op_res_bool[delta > 0]);
773
    default:
774
  msg_panic("%s: unknown operator: %d",
775
      myname, relop);
776
    }
777
}
778
779
int     main(int unused_argc, char **argv)
780
{
781
    VSTRING *buf = vstring_alloc(100);
782
    VSTRING *result = vstring_alloc(100);
783
    char   *cp;
784
    char   *name;
785
    char   *value;
786
    HTABLE *table;
787
    int     stat;
788
    int     length_relops[] = {
789
  MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
790
  MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
791
  MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
792
  0,
793
    };
794
795
    /*
796
     * Add relops that compare string lengths instead of content.
797
     */
798
    mac_expand_add_relop(length_relops, "length", length_relop_eval);
799
800
    /*
801
     * Loop over the inputs.
802
     */
803
    while (!vstream_feof(VSTREAM_IN)) {
804
805
  table = htable_create(0);
806
807
  /*
808
   * Read a block of definitions, terminated with an empty line.
809
   */
810
  while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
811
      vstream_printf("<< %s\n", vstring_str(buf));
812
      vstream_fflush(VSTREAM_OUT);
813
      if (VSTRING_LEN(buf) == 0)
814
    break;
815
      cp = vstring_str(buf);
816
      name = mystrtok(&cp, CHARS_SPACE "=");
817
      value = mystrtok(&cp, CHARS_SPACE "=");
818
      htable_enter(table, name, value ? mystrdup(value) : 0);
819
  }
820
821
  /*
822
   * Read a block of patterns, terminated with an empty line or EOF.
823
   */
824
  while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
825
      vstream_printf("<< %s\n", vstring_str(buf));
826
      vstream_fflush(VSTREAM_OUT);
827
      if (VSTRING_LEN(buf) == 0)
828
    break;
829
      cp = vstring_str(buf);
830
      VSTRING_RESET(result);
831
      stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
832
            (char *) 0, lookup, (void *) table);
833
      vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
834
      vstream_fflush(VSTREAM_OUT);
835
  }
836
  htable_free(table, myfree);
837
  vstream_printf("\n");
838
    }
839
840
    /*
841
     * Clean up.
842
     */
843
    vstring_free(buf);
844
    vstring_free(result);
845
    exit(0);
846
}
847
848
#endif