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