1 /* $NetBSD: bounce_template.c,v 1.1.1.1 2009/06/23 10:08:42 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* bounce_template 3 6 /* SUMMARY 7 /* bounce template support 8 /* SYNOPSIS 9 /* #include <bounce_template.h> 10 /* 11 /* BOUNCE_TEMPLATE *bounce_template_create(def_template) 12 /* const BOUNCE_TEMPLATE *def_template; 13 /* 14 /* void bounce_template_free(template) 15 /* BOUNCE_TEMPLATE *template; 16 /* 17 /* void bounce_template_load(template, stream, buffer, origin) 18 /* BOUNCE_TEMPLATE *template; 19 /* VSTREAM *stream; 20 /* const char *buffer; 21 /* const char *origin; 22 /* 23 /* void bounce_template_headers(out_fn, stream, template, 24 /* rcpt, postmaster_copy) 25 /* int (*out_fn)(VSTREAM *, const char *, ...); 26 /* VSTREAM *stream; 27 /* BOUNCE_TEMPLATE *template; 28 /* const char *rcpt; 29 /* int postmaster_copy; 30 /* 31 /* const char *bounce_template_encoding(template) 32 /* BOUNCE_TEMPLATE *template; 33 /* 34 /* const char *bounce_template_charset(template) 35 /* BOUNCE_TEMPLATE *template; 36 /* 37 /* void bounce_template_expand(out_fn, stream, template) 38 /* int (*out_fn)(VSTREAM *, const char *); 39 /* VSTREAM *stream; 40 /* BOUNCE_TEMPLATE *template; 41 /* 42 /* void bounce_template_dump(stream, template) 43 /* VSTREAM *stream; 44 /* BOUNCE_TEMPLATE *template; 45 /* 46 /* int IS_FAILURE_TEMPLATE(template) 47 /* int IS_DELAY_TEMPLATE(template) 48 /* int IS_SUCCESS_TEMPLATE(template) 49 /* int IS_VERIFY_TEMPLATE(template) 50 /* BOUNCE_TEMPLATE *template; 51 /* DESCRIPTION 52 /* This module implements the built-in and external bounce 53 /* message template support. The content of a template are 54 /* private. To access information within a template, use 55 /* the API described in this document. 56 /* 57 /* bounce_template_create() creates a template, with the 58 /* specified default settings. The template defaults are not 59 /* copied. 60 /* 61 /* bounce_template_free() destroys a bounce message template. 62 /* 63 /* bounce_template_load() overrides a bounce template with the 64 /* specified buffer from the specified origin. The buffer and 65 /* origin are copied. Specify a null buffer and origin pointer 66 /* to reset the template to the defaults specified with 67 /* bounce_template_create(). 68 /* 69 /* bounce_template_headers() sends the postmaster or non-postmaster 70 /* From/Subject/To message headers to the specified stream. 71 /* The recipient address is expected to be in RFC822 external 72 /* form. The postmaster_copy argument is one of POSTMASTER_COPY 73 /* or NO_POSTMASTER_COPY. 74 /* 75 /* bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT 76 /* or MAIL_ATTR_ENC_8BIT) for the bounce template message text. 77 /* 78 /* bounce_template_charset() returns the character set for the 79 /* bounce template message text. 80 /* 81 /* bounce_template_expand() expands the body text of the 82 /* specified template and writes the result to the specified 83 /* stream. 84 /* 85 /* bounce_template_dump() dumps the specified template to the 86 /* specified stream. 87 /* 88 /* The IS_MUMBLE_TEMPLATE() macros are predicates that 89 /* determine whether the template is of the specified type. 90 /* DIAGNOSTICS 91 /* Fatal error: out of memory, undefined macro name in template. 92 /* SEE ALSO 93 /* bounce_templates(3) bounce template group support 94 /* LICENSE 95 /* .ad 96 /* .fi 97 /* The Secure Mailer license must be distributed with this software. 98 /* AUTHOR(S) 99 /* Wietse Venema 100 /* IBM T.J. Watson Research 101 /* P.O. Box 704 102 /* Yorktown Heights, NY 10598, USA 103 /*--*/ 104 105 /* System library. */ 106 107 #include <sys_defs.h> 108 #include <string.h> 109 #include <ctype.h> 110 111 #ifdef STRCASECMP_IN_STRINGS_H 112 #include <strings.h> 113 #endif 114 115 /* Utility library. */ 116 117 #include <msg.h> 118 #include <mac_expand.h> 119 #include <split_at.h> 120 #include <stringops.h> 121 #include <mymalloc.h> 122 123 /* Global library. */ 124 125 #include <mail_params.h> 126 #include <mail_proto.h> 127 #include <mail_conf.h> 128 #include <is_header.h> 129 130 /* Application-specific. */ 131 132 #include <bounce_template.h> 133 134 /* 135 * The following tables implement support for bounce template expansions of 136 * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of 137 * these is the actual parameter value divided by the number of seconds in a 138 * day (hour, etc.), so that we can produce nicely formatted bounce messages 139 * with time values converted into the appropriate units. 140 * 141 * Ideally, the bounce template processor would strip the _days etc. suffix 142 * from the parameter name, and use the parameter name to look up the actual 143 * parameter value and its default value (the default value specifies the 144 * default time unit of that parameter (seconds, minutes, etc.)), and use 145 * this to convert the parameter string value into the corresponding number 146 * of seconds. The bounce template processor would then use the _hours etc. 147 * suffix from the bounce template to divide this number by the number of 148 * seconds in an hour, etc. and produce the number that is needed for the 149 * template. 150 * 151 * Unfortunately, there exists no code to look up default values by parameter 152 * name. If such code existed, then we could do the _days, _hours, etc. 153 * conversion with every main.cf time parameter without having to know in 154 * advance what time parameter names exist. 155 * 156 * So we have to either maintain our own table of all time related main.cf 157 * parameter names and defaults (like the postconf command does) or we make 158 * a special case for a few parameters of special interest. 159 * 160 * We go for the second solution. There are only a few parameters that need 161 * this treatment, and there will be more special cases when individual 162 * queue files get support for individual expiration times, and when other 163 * queue file information needs to be reported in bounce template messages. 164 * 165 * A really lame implementation would simply strip the optional s, h, d, etc. 166 * suffix from the actual (string) parameter value and not do any conversion 167 * at all to hours, days or weeks. But then the information in delay warning 168 * notices could be seriously incorrect. 169 */ 170 typedef struct { 171 const char *suffix; /* days, hours, etc. */ 172 int suffix_len; /* byte count */ 173 int divisor; /* divisor */ 174 } BOUNCE_TIME_DIVISOR; 175 176 #define STRING_AND_LEN(x) (x), (sizeof(x) - 1) 177 178 static const BOUNCE_TIME_DIVISOR time_divisors[] = { 179 STRING_AND_LEN("seconds"), 1, 180 STRING_AND_LEN("minutes"), 60, 181 STRING_AND_LEN("hours"), 60 * 60, 182 STRING_AND_LEN("days"), 24 * 60 * 60, 183 STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60, 184 0, 0, 185 }; 186 187 /* 188 * The few special-case main.cf parameters that have support for _days, etc. 189 * suffixes for automatic conversion when expanded into a bounce template. 190 */ 191 typedef struct { 192 const char *param_name; /* parameter name */ 193 int param_name_len; /* name length */ 194 int *value; /* parameter value */ 195 } BOUNCE_TIME_PARAMETER; 196 197 static const BOUNCE_TIME_PARAMETER time_parameter[] = { 198 STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time, 199 STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time, 200 0, 0, 201 }; 202 203 /* 204 * SLMs. 205 */ 206 #define STR(x) vstring_str(x) 207 208 /* bounce_template_create - create one template */ 209 210 BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype) 211 { 212 BOUNCE_TEMPLATE *tp; 213 214 tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp)); 215 *tp = *prototype; 216 return (tp); 217 } 218 219 /* bounce_template_free - destroy one template */ 220 221 void bounce_template_free(BOUNCE_TEMPLATE *tp) 222 { 223 if (tp->buffer) { 224 myfree(tp->buffer); 225 myfree((char *) tp->origin); 226 } 227 myfree((char *) tp); 228 } 229 230 /* bounce_template_reset - reset template to default */ 231 232 static void bounce_template_reset(BOUNCE_TEMPLATE *tp) 233 { 234 myfree(tp->buffer); 235 myfree((char *) tp->origin); 236 *tp = *(tp->prototype); 237 } 238 239 /* bounce_template_load - override one template */ 240 241 void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin, 242 const char *buffer) 243 { 244 245 /* 246 * Clean up after a previous call. 247 */ 248 if (tp->buffer) 249 bounce_template_reset(tp); 250 251 /* 252 * Postpone the work of template parsing until it is really needed. Most 253 * bounce service calls never need a template. 254 */ 255 if (buffer && origin) { 256 tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER; 257 tp->buffer = mystrdup(buffer); 258 tp->origin = mystrdup(origin); 259 } 260 } 261 262 /* bounce_template_parse_buffer - initialize template */ 263 264 static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp) 265 { 266 char *tval = tp->buffer; 267 char *cp; 268 char **cpp; 269 int cpp_len; 270 int cpp_used; 271 int hlen; 272 char *hval; 273 274 /* 275 * Sanity check. 276 */ 277 if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0) 278 msg_panic("bounce_template_parse_buffer: nothing to do here"); 279 tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER; 280 281 /* 282 * Discard the unusable template and use the default one instead. 283 */ 284 #define CLEANUP_AND_RETURN() do { \ 285 bounce_template_reset(tp); \ 286 return; \ 287 } while (0) 288 289 /* 290 * Parse pseudo-header labels and values. 291 */ 292 #define GETLINE(line, buf) \ 293 (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0) 294 295 while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) { 296 for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++) 297 *hval = 0; 298 if (*hval == 0) { 299 msg_warn("%s: empty \"%s\" header value in %s template " 300 "-- ignoring this template", 301 tp->origin, cp, tp->class); 302 CLEANUP_AND_RETURN(); 303 } 304 if (!allascii(hval)) { 305 msg_warn("%s: non-ASCII \"%s\" header value in %s template " 306 "-- ignoring this template", 307 tp->origin, cp, tp->class); 308 CLEANUP_AND_RETURN(); 309 } 310 if (strcasecmp("charset", cp) == 0) { 311 tp->mime_charset = hval; 312 } else if (strcasecmp("from", cp) == 0) { 313 tp->from = hval; 314 } else if (strcasecmp("subject", cp) == 0) { 315 tp->subject = hval; 316 } else if (strcasecmp("postmaster-subject", cp) == 0) { 317 if (tp->postmaster_subject == 0) { 318 msg_warn("%s: inapplicable \"%s\" header label in %s template " 319 "-- ignoring this template", 320 tp->origin, cp, tp->class); 321 CLEANUP_AND_RETURN(); 322 } 323 tp->postmaster_subject = hval; 324 } else { 325 msg_warn("%s: unknown \"%s\" header label in %s template " 326 "-- ignoring this template", 327 tp->origin, cp, tp->class); 328 CLEANUP_AND_RETURN(); 329 } 330 } 331 332 /* 333 * Skip blank lines between header and message text. 334 */ 335 while (cp && (*cp == 0 || allspace(cp))) 336 (void) GETLINE(cp, tval); 337 if (cp == 0) { 338 msg_warn("%s: missing message text in %s template " 339 "-- ignoring this template", 340 tp->origin, tp->class); 341 CLEANUP_AND_RETURN(); 342 } 343 344 /* 345 * Is this 7bit or 8bit text? If the character set is US-ASCII, then 346 * don't allow 8bit text. Don't assume 8bit when charset was changed. 347 */ 348 #define NON_ASCII(p) ((p) && *(p) && !allascii((p))) 349 350 if (NON_ASCII(cp) || NON_ASCII(tval)) { 351 if (strcasecmp(tp->mime_charset, "us-ascii") == 0) { 352 msg_warn("%s: 8-bit message text in %s template", 353 tp->origin, tp->class); 354 msg_warn("please specify a charset value other than us-ascii"); 355 msg_warn("-- ignoring this template for now"); 356 CLEANUP_AND_RETURN(); 357 } 358 tp->mime_encoding = MAIL_ATTR_ENC_8BIT; 359 } 360 361 /* 362 * Collect the message text and null-terminate the result. 363 */ 364 cpp_len = 10; 365 cpp_used = 0; 366 cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len); 367 while (cp) { 368 cpp[cpp_used++] = cp; 369 if (cpp_used >= cpp_len) { 370 cpp = (char **) myrealloc((char *) cpp, 371 sizeof(*cpp) * 2 * cpp_len); 372 cpp_len *= 2; 373 } 374 (void) GETLINE(cp, tval); 375 } 376 cpp[cpp_used] = 0; 377 tp->message_text = (const char **) cpp; 378 } 379 380 /* bounce_template_lookup - lookup $name value */ 381 382 static const char *bounce_template_lookup(const char *key, int unused_mode, 383 char *context) 384 { 385 BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context; 386 const BOUNCE_TIME_PARAMETER *bp; 387 const BOUNCE_TIME_DIVISOR *bd; 388 static VSTRING *buf; 389 int result; 390 391 /* 392 * Look for parameter names that can have a time unit suffix, and scale 393 * the time value according to the suffix. 394 */ 395 for (bp = time_parameter; bp->param_name; bp++) { 396 if (strncmp(key, bp->param_name, bp->param_name_len) == 0 397 && key[bp->param_name_len] == '_') { 398 for (bd = time_divisors; bd->suffix; bd++) { 399 if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) { 400 result = bp->value[0] / bd->divisor; 401 if (result > 999 && bd->divisor < 86400) { 402 msg_warn("%s: excessive result \"%d\" in %s " 403 "template conversion of parameter \"%s\"", 404 tp->origin, result, tp->class, key); 405 msg_warn("please increase time unit \"%s\" of \"%s\" " 406 "in %s template", bd->suffix, key, tp->class); 407 msg_warn("for instructions see the bounce(5) manual"); 408 } else if (result == 0 && bp->value[0] && bd->divisor > 1) { 409 msg_warn("%s: zero result in %s template " 410 "conversion of parameter \"%s\"", 411 tp->origin, tp->class, key); 412 msg_warn("please reduce time unit \"%s\" of \"%s\" " 413 "in %s template", bd->suffix, key, tp->class); 414 msg_warn("for instructions see the bounce(5) manual"); 415 } 416 if (buf == 0) 417 buf = vstring_alloc(10); 418 vstring_sprintf(buf, "%d", result); 419 return (STR(buf)); 420 } 421 } 422 msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"", 423 tp->origin, 424 key + bp->param_name_len + 1, key); 425 } 426 } 427 return (mail_conf_lookup_eval(key)); 428 } 429 430 /* bounce_template_headers - send template headers */ 431 432 void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp, 433 BOUNCE_TEMPLATE *tp, 434 const char *rcpt, 435 int postmaster_copy) 436 { 437 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 438 bounce_template_parse_buffer(tp); 439 440 out_fn(fp, "From: %s", tp->from); 441 out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ? 442 tp->postmaster_subject : tp->subject); 443 out_fn(fp, "To: %s", rcpt); 444 } 445 446 /* bounce_template_expand - expand template to stream */ 447 448 void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp, 449 BOUNCE_TEMPLATE *tp) 450 { 451 VSTRING *buf = vstring_alloc(100); 452 const char **cpp; 453 int stat; 454 const char *filter = "\t !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; 455 456 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 457 bounce_template_parse_buffer(tp); 458 459 for (cpp = tp->message_text; *cpp; cpp++) { 460 stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_NONE, filter, 461 bounce_template_lookup, (char *) tp); 462 if (stat & MAC_PARSE_ERROR) 463 msg_fatal("%s: bad $name syntax in %s template: %s", 464 tp->origin, tp->class, *cpp); 465 if (stat & MAC_PARSE_UNDEF) 466 msg_fatal("%s: undefined $name in %s template: %s", 467 tp->origin, tp->class, *cpp); 468 out_fn(fp, STR(buf)); 469 } 470 vstring_free(buf); 471 } 472 473 /* bounce_template_dump - dump template to stream */ 474 475 void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp) 476 { 477 const char **cpp; 478 479 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 480 bounce_template_parse_buffer(tp); 481 482 vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset); 483 vstream_fprintf(fp, "From: %s\n", tp->from); 484 vstream_fprintf(fp, "Subject: %s\n", tp->subject); 485 if (tp->postmaster_subject) 486 vstream_fprintf(fp, "Postmaster-Subject: %s\n", 487 tp->postmaster_subject); 488 vstream_fprintf(fp, "\n"); 489 for (cpp = tp->message_text; *cpp; cpp++) 490 vstream_fprintf(fp, "%s\n", *cpp); 491 } 492