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