1 /* $NetBSD: bounce_template.c,v 1.2 2017/02/14 01:16:44 christos 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 #ifndef NO_EAI 123 #include <midna_domain.h> 124 #endif 125 126 /* Global library. */ 127 128 #include <mail_params.h> 129 #include <mail_proto.h> 130 #include <mail_conf.h> 131 #include <is_header.h> 132 133 /* Application-specific. */ 134 135 #include <bounce_template.h> 136 137 /* 138 * The following tables implement support for bounce template expansions of 139 * $<parameter_name>_days ($<parameter_name>_hours, etc.). The expansion of 140 * these is the actual parameter value divided by the number of seconds in a 141 * day (hour, etc.), so that we can produce nicely formatted bounce messages 142 * with time values converted into the appropriate units. 143 * 144 * Ideally, the bounce template processor would strip the _days etc. suffix 145 * from the parameter name, and use the parameter name to look up the actual 146 * parameter value and its default value (the default value specifies the 147 * default time unit of that parameter (seconds, minutes, etc.)), and use 148 * this to convert the parameter string value into the corresponding number 149 * of seconds. The bounce template processor would then use the _hours etc. 150 * suffix from the bounce template to divide this number by the number of 151 * seconds in an hour, etc. and produce the number that is needed for the 152 * template. 153 * 154 * Unfortunately, there exists no code to look up default values by parameter 155 * name. If such code existed, then we could do the _days, _hours, etc. 156 * conversion with every main.cf time parameter without having to know in 157 * advance what time parameter names exist. 158 * 159 * So we have to either maintain our own table of all time related main.cf 160 * parameter names and defaults (like the postconf command does) or we make 161 * a special case for a few parameters of special interest. 162 * 163 * We go for the second solution. There are only a few parameters that need 164 * this treatment, and there will be more special cases when individual 165 * queue files get support for individual expiration times, and when other 166 * queue file information needs to be reported in bounce template messages. 167 * 168 * A really lame implementation would simply strip the optional s, h, d, etc. 169 * suffix from the actual (string) parameter value and not do any conversion 170 * at all to hours, days or weeks. But then the information in delay warning 171 * notices could be seriously incorrect. 172 */ 173 typedef struct { 174 const char *suffix; /* days, hours, etc. */ 175 int suffix_len; /* byte count */ 176 int divisor; /* divisor */ 177 } BOUNCE_TIME_DIVISOR; 178 179 #define STRING_AND_LEN(x) (x), (sizeof(x) - 1) 180 181 static const BOUNCE_TIME_DIVISOR time_divisors[] = { 182 STRING_AND_LEN("seconds"), 1, 183 STRING_AND_LEN("minutes"), 60, 184 STRING_AND_LEN("hours"), 60 * 60, 185 STRING_AND_LEN("days"), 24 * 60 * 60, 186 STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60, 187 0, 0, 188 }; 189 190 /* 191 * The few special-case main.cf parameters that have support for _days, etc. 192 * suffixes for automatic conversion when expanded into a bounce template. 193 */ 194 typedef struct { 195 const char *param_name; /* parameter name */ 196 int param_name_len; /* name length */ 197 int *value; /* parameter value */ 198 } BOUNCE_TIME_PARAMETER; 199 200 static const BOUNCE_TIME_PARAMETER time_parameter[] = { 201 STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time, 202 STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time, 203 0, 0, 204 }; 205 206 /* 207 * Parameters whose value may have to be converted to UTF-8 for presentation 208 * purposes. 209 */ 210 typedef struct { 211 const char *param_name; /* parameter name */ 212 char **value; /* parameter value */ 213 } BOUNCE_STR_PARAMETER; 214 215 static const BOUNCE_STR_PARAMETER str_parameter[] = { 216 VAR_MYHOSTNAME, &var_myhostname, 217 VAR_MYDOMAIN, &var_mydomain, 218 0, 0, 219 }; 220 221 /* 222 * SLMs. 223 */ 224 #define STR(x) vstring_str(x) 225 226 /* bounce_template_create - create one template */ 227 228 BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype) 229 { 230 BOUNCE_TEMPLATE *tp; 231 232 tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp)); 233 *tp = *prototype; 234 return (tp); 235 } 236 237 /* bounce_template_free - destroy one template */ 238 239 void bounce_template_free(BOUNCE_TEMPLATE *tp) 240 { 241 if (tp->buffer) { 242 myfree(tp->buffer); 243 myfree((void *) tp->origin); 244 } 245 myfree((void *) tp); 246 } 247 248 /* bounce_template_reset - reset template to default */ 249 250 static void bounce_template_reset(BOUNCE_TEMPLATE *tp) 251 { 252 myfree(tp->buffer); 253 myfree((void *) tp->origin); 254 *tp = *(tp->prototype); 255 } 256 257 /* bounce_template_load - override one template */ 258 259 void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin, 260 const char *buffer) 261 { 262 263 /* 264 * Clean up after a previous call. 265 */ 266 if (tp->buffer) 267 bounce_template_reset(tp); 268 269 /* 270 * Postpone the work of template parsing until it is really needed. Most 271 * bounce service calls never need a template. 272 */ 273 if (buffer && origin) { 274 tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER; 275 tp->buffer = mystrdup(buffer); 276 tp->origin = mystrdup(origin); 277 } 278 } 279 280 /* bounce_template_parse_buffer - initialize template */ 281 282 static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp) 283 { 284 char *tval = tp->buffer; 285 char *cp; 286 char **cpp; 287 int cpp_len; 288 int cpp_used; 289 int hlen; 290 char *hval; 291 292 /* 293 * Sanity check. 294 */ 295 if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0) 296 msg_panic("bounce_template_parse_buffer: nothing to do here"); 297 tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER; 298 299 /* 300 * Discard the unusable template and use the default one instead. 301 */ 302 #define CLEANUP_AND_RETURN() do { \ 303 bounce_template_reset(tp); \ 304 return; \ 305 } while (0) 306 307 /* 308 * Parse pseudo-header labels and values. 309 * 310 * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8 311 * message. Sending SMTPUTF8 in reponse to non-SMTPUTF8 mail would make 312 * no sense. 313 */ 314 #define GETLINE(line, buf) \ 315 (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0) 316 317 while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) { 318 for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++) 319 *hval = 0; 320 if (*hval == 0) { 321 msg_warn("%s: empty \"%s\" header value in %s template " 322 "-- ignoring this template", 323 tp->origin, cp, tp->class); 324 CLEANUP_AND_RETURN(); 325 } 326 if (!allascii(hval)) { 327 msg_warn("%s: non-ASCII \"%s\" header value in %s template " 328 "-- ignoring this template", 329 tp->origin, cp, tp->class); 330 CLEANUP_AND_RETURN(); 331 } 332 if (strcasecmp("charset", cp) == 0) { 333 tp->mime_charset = hval; 334 } else if (strcasecmp("from", cp) == 0) { 335 tp->from = hval; 336 } else if (strcasecmp("subject", cp) == 0) { 337 tp->subject = hval; 338 } else if (strcasecmp("postmaster-subject", cp) == 0) { 339 if (tp->postmaster_subject == 0) { 340 msg_warn("%s: inapplicable \"%s\" header label in %s template " 341 "-- ignoring this template", 342 tp->origin, cp, tp->class); 343 CLEANUP_AND_RETURN(); 344 } 345 tp->postmaster_subject = hval; 346 } else { 347 msg_warn("%s: unknown \"%s\" header label in %s template " 348 "-- ignoring this template", 349 tp->origin, cp, tp->class); 350 CLEANUP_AND_RETURN(); 351 } 352 } 353 354 /* 355 * Skip blank lines between header and message text. 356 */ 357 while (cp && (*cp == 0 || allspace(cp))) 358 (void) GETLINE(cp, tval); 359 if (cp == 0) { 360 msg_warn("%s: missing message text in %s template " 361 "-- ignoring this template", 362 tp->origin, tp->class); 363 CLEANUP_AND_RETURN(); 364 } 365 366 /* 367 * Is this 7bit or 8bit text? If the character set is US-ASCII, then 368 * don't allow 8bit text. Don't assume 8bit when charset was changed. 369 */ 370 #define NON_ASCII(p) ((p) && *(p) && !allascii((p))) 371 372 if (NON_ASCII(cp) || NON_ASCII(tval)) { 373 if (strcasecmp(tp->mime_charset, "us-ascii") == 0) { 374 msg_warn("%s: 8-bit message text in %s template", 375 tp->origin, tp->class); 376 msg_warn("please specify a charset value other than us-ascii"); 377 msg_warn("-- ignoring this template for now"); 378 CLEANUP_AND_RETURN(); 379 } 380 tp->mime_encoding = MAIL_ATTR_ENC_8BIT; 381 } 382 383 /* 384 * Collect the message text and null-terminate the result. 385 */ 386 cpp_len = 10; 387 cpp_used = 0; 388 cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len); 389 while (cp) { 390 cpp[cpp_used++] = cp; 391 if (cpp_used >= cpp_len) { 392 cpp = (char **) myrealloc((void *) cpp, 393 sizeof(*cpp) * 2 * cpp_len); 394 cpp_len *= 2; 395 } 396 (void) GETLINE(cp, tval); 397 } 398 cpp[cpp_used] = 0; 399 tp->message_text = (const char **) cpp; 400 } 401 402 /* bounce_template_lookup - lookup $name value */ 403 404 static const char *bounce_template_lookup(const char *key, int unused_mode, 405 void *context) 406 { 407 BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context; 408 const BOUNCE_TIME_PARAMETER *bp; 409 const BOUNCE_TIME_DIVISOR *bd; 410 const BOUNCE_STR_PARAMETER *sp; 411 static VSTRING *buf; 412 int result; 413 const char *asc_val; 414 const char *utf8_val; 415 416 /* 417 * Look for parameter names that can have a time unit suffix, and scale 418 * the time value according to the suffix. 419 */ 420 for (bp = time_parameter; bp->param_name; bp++) { 421 if (strncmp(key, bp->param_name, bp->param_name_len) == 0 422 && key[bp->param_name_len] == '_') { 423 for (bd = time_divisors; bd->suffix; bd++) { 424 if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) { 425 result = bp->value[0] / bd->divisor; 426 if (result > 999 && bd->divisor < 86400) { 427 msg_warn("%s: excessive result \"%d\" in %s " 428 "template conversion of parameter \"%s\"", 429 tp->origin, result, tp->class, key); 430 msg_warn("please increase time unit \"%s\" of \"%s\" " 431 "in %s template", bd->suffix, key, tp->class); 432 msg_warn("for instructions see the bounce(5) manual"); 433 } else if (result == 0 && bp->value[0] && bd->divisor > 1) { 434 msg_warn("%s: zero result in %s template " 435 "conversion of parameter \"%s\"", 436 tp->origin, tp->class, key); 437 msg_warn("please reduce time unit \"%s\" of \"%s\" " 438 "in %s template", bd->suffix, key, tp->class); 439 msg_warn("for instructions see the bounce(5) manual"); 440 } 441 if (buf == 0) 442 buf = vstring_alloc(10); 443 vstring_sprintf(buf, "%d", result); 444 return (STR(buf)); 445 } 446 } 447 msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"", 448 tp->origin, 449 key + bp->param_name_len + 1, key); 450 } 451 } 452 453 /* 454 * Look for parameter names that may have to be up-converted for 455 * presentation purposes. 456 */ 457 #ifndef NO_EAI 458 if (var_smtputf8_enable) { 459 for (sp = str_parameter; sp->param_name; sp++) { 460 if (strcmp(key, sp->param_name) == 0) { 461 asc_val = sp->value[0]; 462 if (!allascii(asc_val)) { 463 msg_warn("%s: conversion \"%s\" failed: " 464 "non-ASCII input value: \"%s\"", 465 tp->origin, key, asc_val); 466 return (asc_val); 467 } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) { 468 msg_warn("%s: conversion \"%s\" failed: " 469 "input value: \"%s\"", 470 tp->origin, key, asc_val); 471 return (asc_val); 472 } else { 473 return (utf8_val); 474 } 475 } 476 } 477 } 478 #endif 479 return (mail_conf_lookup_eval(key)); 480 } 481 482 /* bounce_template_headers - send template headers */ 483 484 void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp, 485 BOUNCE_TEMPLATE *tp, 486 const char *rcpt, 487 int postmaster_copy) 488 { 489 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 490 bounce_template_parse_buffer(tp); 491 492 out_fn(fp, "From: %s", tp->from); 493 out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ? 494 tp->postmaster_subject : tp->subject); 495 out_fn(fp, "To: %s", rcpt); 496 } 497 498 /* bounce_template_expand - expand template to stream */ 499 500 void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp, 501 BOUNCE_TEMPLATE *tp) 502 { 503 VSTRING *buf = vstring_alloc(100); 504 const char **cpp; 505 int stat; 506 507 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 508 bounce_template_parse_buffer(tp); 509 510 for (cpp = tp->message_text; *cpp; cpp++) { 511 stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0, 512 bounce_template_lookup, (void *) tp); 513 if (stat & MAC_PARSE_ERROR) 514 msg_fatal("%s: bad $name syntax in %s template: %s", 515 tp->origin, tp->class, *cpp); 516 if (stat & MAC_PARSE_UNDEF) 517 msg_fatal("%s: undefined $name in %s template: %s", 518 tp->origin, tp->class, *cpp); 519 out_fn(fp, STR(buf)); 520 } 521 vstring_free(buf); 522 } 523 524 /* bounce_template_dump - dump template to stream */ 525 526 void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp) 527 { 528 const char **cpp; 529 530 if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) 531 bounce_template_parse_buffer(tp); 532 533 vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset); 534 vstream_fprintf(fp, "From: %s\n", tp->from); 535 vstream_fprintf(fp, "Subject: %s\n", tp->subject); 536 if (tp->postmaster_subject) 537 vstream_fprintf(fp, "Postmaster-Subject: %s\n", 538 tp->postmaster_subject); 539 vstream_fprintf(fp, "\n"); 540 for (cpp = tp->message_text; *cpp; cpp++) 541 vstream_fprintf(fp, "%s\n", *cpp); 542 } 543