1 /* $NetBSD: cleanup_message.c,v 1.5 2025/02/25 19:15:44 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* cleanup_message 3
6 /* SUMMARY
7 /* process message segment
8 /* SYNOPSIS
9 /* #include "cleanup.h"
10 /*
11 /* void cleanup_message(state, type, buf, len)
12 /* CLEANUP_STATE *state;
13 /* int type;
14 /* const char *buf;
15 /* ssize_t len;
16 /* DESCRIPTION
17 /* This module processes message content records and copies the
18 /* result to the queue file. It validates the input, rewrites
19 /* sender/recipient addresses to canonical form, inserts missing
20 /* message headers, and extracts information from message headers
21 /* to be used later when generating the extracted output segment.
22 /* This routine absorbs but does not emit the content to extracted
23 /* boundary record.
24 /*
25 /* Arguments:
26 /* .IP state
27 /* Queue file and message processing state. This state is updated
28 /* as records are processed and as errors happen.
29 /* .IP type
30 /* Record type.
31 /* .IP buf
32 /* Record content.
33 /* .IP len
34 /* Record content length.
35 /* LICENSE
36 /* .ad
37 /* .fi
38 /* The Secure Mailer license must be distributed with this software.
39 /* AUTHOR(S)
40 /* Wietse Venema
41 /* IBM T.J. Watson Research
42 /* P.O. Box 704
43 /* Yorktown Heights, NY 10598, USA
44 /*
45 /* Wietse Venema
46 /* Google, Inc.
47 /* 111 8th Avenue
48 /* New York, NY 10011, USA
49 /*
50 /* Wietse Venema
51 /* porcupine.org
52 /*--*/
53
54 /* System library. */
55
56 #include <sys_defs.h>
57 #include <ctype.h>
58 #include <string.h>
59 #include <time.h>
60 #include <unistd.h>
61
62 #ifdef STRCASECMP_IN_STRINGS_H
63 #include <strings.h>
64 #endif
65
66 /* Utility library. */
67
68 #include <msg.h>
69 #include <vstring.h>
70 #include <vstream.h>
71 #include <argv.h>
72 #include <split_at.h>
73 #include <mymalloc.h>
74 #include <stringops.h>
75 #include <nvtable.h>
76 #include <clean_ascii_cntrl_space.h>
77
78 /* Global library. */
79
80 #include <ascii_header_text.h>
81 #include <record.h>
82 #include <rec_type.h>
83 #include <cleanup_user.h>
84 #include <tok822.h>
85 #include <lex_822.h>
86 #include <header_opts.h>
87 #include <quote_822_local.h>
88 #include <mail_params.h>
89 #include <mail_date.h>
90 #include <mail_addr.h>
91 #include <is_header.h>
92 #include <ext_prop.h>
93 #include <mail_proto.h>
94 #include <mime_state.h>
95 #include <lex_822.h>
96 #include <dsn_util.h>
97 #include <conv_time.h>
98 #include <info_log_addr_form.h>
99 #include <hfrom_format.h>
100 #include <rfc2047_code.h>
101 #include <sendopts.h>
102
103 /* Application-specific. */
104
105 #include "cleanup.h"
106
107 /* cleanup_fold_header - wrap address list header */
108
cleanup_fold_header(CLEANUP_STATE * state,VSTRING * header_buf)109 static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf)
110 {
111 char *start_line = vstring_str(header_buf);
112 char *end_line;
113 char *next_line;
114 char *line;
115
116 /*
117 * A rewritten address list contains one address per line. The code below
118 * replaces newlines by spaces, to fit as many addresses on a line as
119 * possible (without rearranging the order of addresses). Prepending
120 * white space to the beginning of lines is delegated to the output
121 * routine.
122 */
123 for (line = start_line; line != 0; line = next_line) {
124 end_line = line + strcspn(line, "\n");
125 if (line > start_line) {
126 if (end_line - start_line < 70) { /* TAB counts as one */
127 line[-1] = ' ';
128 } else {
129 start_line = line;
130 }
131 }
132 next_line = *end_line ? end_line + 1 : 0;
133 }
134 cleanup_out_header(state, header_buf);
135 }
136
137 /* cleanup_extract_internal - save unquoted copy of extracted address */
138
cleanup_extract_internal(VSTRING * buffer,TOK822 * addr)139 static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr)
140 {
141
142 /*
143 * A little routine to stash away a copy of an address that we extracted
144 * from a message header line.
145 */
146 tok822_internalize(buffer, addr->head, TOK822_STR_DEFL);
147 return (mystrdup(vstring_str(buffer)));
148 }
149
150 /* cleanup_rewrite_sender - sender address rewriting */
151
cleanup_rewrite_sender(CLEANUP_STATE * state,const HEADER_OPTS * hdr_opts,VSTRING * header_buf)152 static void cleanup_rewrite_sender(CLEANUP_STATE *state,
153 const HEADER_OPTS *hdr_opts,
154 VSTRING *header_buf)
155 {
156 TOK822 *tree;
157 TOK822 **addr_list;
158 TOK822 **tpp;
159 int did_rewrite = 0;
160
161 if (msg_verbose)
162 msg_info("rewrite_sender: %s", hdr_opts->name);
163
164 /*
165 * Parse the header line, rewrite each address found, and regenerate the
166 * header line. Finally, pipe the result through the header line folding
167 * routine.
168 */
169 tree = tok822_parse_limit(vstring_str(header_buf)
170 + strlen(hdr_opts->name) + 1,
171 var_token_limit);
172 addr_list = tok822_grep(tree, TOK822_ADDR);
173 for (tpp = addr_list; *tpp; tpp++) {
174 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
175 if (state->flags & CLEANUP_FLAG_MAP_OK) {
176 if (cleanup_send_canon_maps
177 && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
178 did_rewrite |=
179 cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps,
180 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
181 if (cleanup_comm_canon_maps
182 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
183 did_rewrite |=
184 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
185 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
186 if (cleanup_masq_domains
187 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM))
188 did_rewrite |=
189 cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
190 }
191 }
192 if (did_rewrite) {
193 vstring_truncate(header_buf, strlen(hdr_opts->name));
194 vstring_strcat(header_buf, ": ");
195 tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
196 }
197 myfree((void *) addr_list);
198 tok822_free_tree(tree);
199 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
200 if (did_rewrite)
201 cleanup_fold_header(state, header_buf);
202 else
203 cleanup_out_header(state, header_buf);
204 }
205 }
206
207 /* cleanup_rewrite_recip - recipient address rewriting */
208
cleanup_rewrite_recip(CLEANUP_STATE * state,const HEADER_OPTS * hdr_opts,VSTRING * header_buf)209 static void cleanup_rewrite_recip(CLEANUP_STATE *state,
210 const HEADER_OPTS *hdr_opts,
211 VSTRING *header_buf)
212 {
213 TOK822 *tree;
214 TOK822 **addr_list;
215 TOK822 **tpp;
216 int did_rewrite = 0;
217
218 if (msg_verbose)
219 msg_info("rewrite_recip: %s", hdr_opts->name);
220
221 /*
222 * Parse the header line, rewrite each address found, and regenerate the
223 * header line. Finally, pipe the result through the header line folding
224 * routine.
225 */
226 tree = tok822_parse_limit(vstring_str(header_buf)
227 + strlen(hdr_opts->name) + 1,
228 var_token_limit);
229 addr_list = tok822_grep(tree, TOK822_ADDR);
230 for (tpp = addr_list; *tpp; tpp++) {
231 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
232 if (state->flags & CLEANUP_FLAG_MAP_OK) {
233 if (cleanup_rcpt_canon_maps
234 && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
235 did_rewrite |=
236 cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps,
237 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
238 if (cleanup_comm_canon_maps
239 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
240 did_rewrite |=
241 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
242 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
243 if (cleanup_masq_domains
244 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT))
245 did_rewrite |=
246 cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
247 }
248 }
249 if (did_rewrite) {
250 vstring_truncate(header_buf, strlen(hdr_opts->name));
251 vstring_strcat(header_buf, ": ");
252 tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
253 }
254 myfree((void *) addr_list);
255 tok822_free_tree(tree);
256 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
257 if (did_rewrite)
258 cleanup_fold_header(state, header_buf);
259 else
260 cleanup_out_header(state, header_buf);
261 }
262 }
263
264 /* cleanup_act_log - log action with context */
265
cleanup_act_log(CLEANUP_STATE * state,const char * action,const char * class,const char * content,const char * text)266 static void cleanup_act_log(CLEANUP_STATE *state,
267 const char *action, const char *class,
268 const char *content, const char *text)
269 {
270 const char *attr;
271
272 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
273 attr = "unknown";
274 vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;",
275 state->queue_id, action, class, content, attr);
276 if (state->sender)
277 vstring_sprintf_append(state->temp1, " from=<%s>",
278 info_log_addr_form_sender(state->sender));
279 if (state->recip)
280 vstring_sprintf_append(state->temp1, " to=<%s>",
281 info_log_addr_form_recipient(state->recip));
282 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
283 vstring_sprintf_append(state->temp1, " proto=%s", attr);
284 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
285 vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
286 if (text && *text)
287 vstring_sprintf_append(state->temp1, ": %s", text);
288 msg_info("%s", vstring_str(state->temp1));
289 }
290
291 #define CLEANUP_ACT_CTXT_HEADER "header"
292 #define CLEANUP_ACT_CTXT_BODY "body"
293 #define CLEANUP_ACT_CTXT_ANY "content"
294
295 /* cleanup_act - act upon a header/body match */
296
cleanup_act(CLEANUP_STATE * state,char * context,const char * buf,const char * value,const char * map_class)297 static const char *cleanup_act(CLEANUP_STATE *state, char *context,
298 const char *buf, const char *value,
299 const char *map_class)
300 {
301 const char *optional_text = value + strcspn(value, " \t");
302 int command_len = optional_text - value;
303
304 #ifdef DELAY_ACTION
305 int defer_delay;
306
307 #endif
308
309 while (*optional_text && ISSPACE(*optional_text))
310 optional_text++;
311
312 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
313 #define CLEANUP_ACT_DROP 0
314
315 /*
316 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
317 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
318 * queue record processing, and prevents bounces from being sent.
319 */
320 if (STREQUAL(value, "REJECT", command_len)) {
321 const CLEANUP_STAT_DETAIL *detail;
322
323 if (state->reason)
324 myfree(state->reason);
325 if (*optional_text) {
326 state->reason = dsn_prepend("5.7.1", optional_text);
327 if (*state->reason != '4' && *state->reason != '5') {
328 msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
329 optional_text);
330 *state->reason = '4';
331 }
332 } else {
333 detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
334 state->reason = dsn_prepend(detail->dsn, detail->text);
335 }
336 if (*state->reason == '4')
337 state->errs |= CLEANUP_STAT_DEFER;
338 else
339 state->errs |= CLEANUP_STAT_CONT;
340 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
341 cleanup_act_log(state, "reject", context, buf, state->reason);
342 return (buf);
343 }
344 if (STREQUAL(value, "WARN", command_len)) {
345 cleanup_act_log(state, "warning", context, buf, optional_text);
346 return (buf);
347 }
348 if (STREQUAL(value, "INFO", command_len)) {
349 cleanup_act_log(state, "info", context, buf, optional_text);
350 return (buf);
351 }
352 if (STREQUAL(value, "FILTER", command_len)) {
353 if (*optional_text == 0) {
354 msg_warn("missing FILTER command argument in %s map", map_class);
355 } else if (strchr(optional_text, ':') == 0) {
356 msg_warn("bad FILTER command %s in %s -- "
357 "need transport:destination",
358 optional_text, map_class);
359 } else {
360 if (state->filter)
361 myfree(state->filter);
362 state->filter = mystrdup(optional_text);
363 cleanup_act_log(state, "filter", context, buf, optional_text);
364 }
365 return (buf);
366 }
367 if (STREQUAL(value, "PASS", command_len)) {
368 cleanup_act_log(state, "pass", context, buf, optional_text);
369 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
370 return (buf);
371 }
372 if (STREQUAL(value, "DISCARD", command_len)) {
373 cleanup_act_log(state, "discard", context, buf, optional_text);
374 state->flags |= CLEANUP_FLAG_DISCARD;
375 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
376 return (buf);
377 }
378 if (STREQUAL(value, "HOLD", command_len)) {
379 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
380 cleanup_act_log(state, "hold", context, buf, optional_text);
381 state->flags |= CLEANUP_FLAG_HOLD;
382 }
383 return (buf);
384 }
385
386 /*
387 * The DELAY feature is disabled because it has too many problems. 1) It
388 * does not work on some remote file systems; 2) mail will be delivered
389 * anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs
390 * down the deferred queue scan with huge amounts of useless disk I/O
391 * operations.
392 */
393 #ifdef DELAY_ACTION
394 if (STREQUAL(value, "DELAY", command_len)) {
395 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
396 if (*optional_text == 0) {
397 msg_warn("missing DELAY argument in %s map", map_class);
398 } else if (conv_time(optional_text, &defer_delay, 's') == 0) {
399 msg_warn("ignoring bad DELAY argument %s in %s map",
400 optional_text, map_class);
401 } else {
402 cleanup_act_log(state, "delay", context, buf, optional_text);
403 state->defer_delay = defer_delay;
404 }
405 }
406 return (buf);
407 }
408 #endif
409 if (STREQUAL(value, "PREPEND", command_len)) {
410 if (*optional_text == 0) {
411 msg_warn("PREPEND action without text in %s map", map_class);
412 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) {
413 if (!is_header(optional_text)) {
414 msg_warn("bad PREPEND header text \"%s\" in %s map -- "
415 "need \"headername: headervalue\"",
416 optional_text, map_class);
417 }
418
419 /*
420 * By design, cleanup_out_header() may modify content. Play safe
421 * and prepare for future developments.
422 */
423 else {
424 VSTRING *temp;
425
426 cleanup_act_log(state, "prepend", context, buf, optional_text);
427 temp = vstring_strcpy(vstring_alloc(strlen(optional_text)),
428 optional_text);
429 cleanup_out_header(state, temp);
430 vstring_free(temp);
431 }
432 } else {
433 cleanup_act_log(state, "prepend", context, buf, optional_text);
434 cleanup_out_string(state, REC_TYPE_NORM, optional_text);
435 }
436 return (buf);
437 }
438 if (STREQUAL(value, "REPLACE", command_len)) {
439 if (*optional_text == 0) {
440 msg_warn("REPLACE action without text in %s map", map_class);
441 return (buf);
442 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
443 && !is_header(optional_text)) {
444 msg_warn("bad REPLACE header text \"%s\" in %s map -- "
445 "need \"headername: headervalue\"",
446 optional_text, map_class);
447 return (buf);
448 } else {
449 cleanup_act_log(state, "replace", context, buf, optional_text);
450 return (mystrdup(optional_text));
451 }
452 }
453 if (STREQUAL(value, "REDIRECT", command_len)) {
454 if (strchr(optional_text, '@') == 0) {
455 msg_warn("bad REDIRECT target \"%s\" in %s map -- "
456 "need user@domain",
457 optional_text, map_class);
458 } else {
459 if (state->redirect)
460 myfree(state->redirect);
461 state->redirect = mystrdup(optional_text);
462 cleanup_act_log(state, "redirect", context, buf, optional_text);
463 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
464 }
465 return (buf);
466 }
467 if (STREQUAL(value, "BCC", command_len)) {
468 if (strchr(optional_text, '@') == 0) {
469 msg_warn("bad BCC address \"%s\" in %s map -- "
470 "need user@domain",
471 optional_text, map_class);
472 } else {
473 if (state->hbc_rcpt == 0)
474 state->hbc_rcpt = argv_alloc(1);
475 argv_add(state->hbc_rcpt, optional_text, (char *) 0);
476 cleanup_act_log(state, "bcc", context, buf, optional_text);
477 }
478 return (buf);
479 }
480 if (STREQUAL(value, "STRIP", command_len)) {
481 cleanup_act_log(state, "strip", context, buf, optional_text);
482 return (CLEANUP_ACT_DROP);
483 }
484 /* Allow and ignore optional text after the action. */
485
486 if (STREQUAL(value, "IGNORE", command_len))
487 return (CLEANUP_ACT_DROP);
488
489 if (STREQUAL(value, "DUNNO", command_len)) /* preferred */
490 return (buf);
491
492 if (STREQUAL(value, "OK", command_len)) /* compat */
493 return (buf);
494
495 msg_warn("unknown command in %s map: %s", map_class, value);
496 return (buf);
497 }
498
499 /* cleanup_header_callback - process one complete header line */
500
cleanup_header_callback(void * context,int header_class,const HEADER_OPTS * hdr_opts,VSTRING * header_buf,off_t unused_offset)501 static void cleanup_header_callback(void *context, int header_class,
502 const HEADER_OPTS *hdr_opts,
503 VSTRING *header_buf,
504 off_t unused_offset)
505 {
506 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
507 const char *myname = "cleanup_header_callback";
508 char *hdrval;
509 struct code_map {
510 const char *name;
511 const char *encoding;
512 };
513 static struct code_map code_map[] = { /* RFC 2045 */
514 "7bit", MAIL_ATTR_ENC_7BIT,
515 "8bit", MAIL_ATTR_ENC_8BIT,
516 "binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */
517 "quoted-printable", MAIL_ATTR_ENC_7BIT,
518 "base64", MAIL_ATTR_ENC_7BIT,
519 0,
520 };
521 struct code_map *cmp;
522 MAPS *checks;
523 const char *map_class;
524
525 if (msg_verbose)
526 msg_info("%s: '%.200s'", myname, vstring_str(header_buf));
527
528 /*
529 * Crude header filtering. This stops malware that isn't sophisticated
530 * enough to use fancy header encodings.
531 */
532 #define CHECK(class, maps, var_name) \
533 (header_class == class && (map_class = var_name, checks = maps) != 0)
534
535 if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
536 header_class = MIME_HDR_MULTIPART;
537
538 /* Update the Received: header count before maybe dropping headers below. */
539 if (hdr_opts && hdr_opts->type == HDR_RECEIVED)
540 state->hop_count += 1;
541
542 if ((state->flags & CLEANUP_FLAG_FILTER)
543 && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS)
544 || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS)
545 || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) {
546 char *header = vstring_str(header_buf);
547 const char *value;
548
549 if ((value = maps_find(checks, header, 0)) != 0) {
550 const char *result;
551
552 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
553 header, value, map_class))
554 == CLEANUP_ACT_DROP) {
555 return;
556 } else if (result != header) {
557 vstring_strcpy(header_buf, result);
558 hdr_opts = header_opts_find(result);
559 myfree((void *) result);
560 }
561 } else if (checks->error) {
562 msg_warn("%s: %s map lookup problem -- "
563 "message not accepted, try again later",
564 state->queue_id, checks->title);
565 state->errs |= CLEANUP_STAT_WRITE;
566 }
567 }
568
569 /*
570 * If this is an "unknown" header, just copy it to the output without
571 * even bothering to fold long lines. cleanup_out() will split long
572 * headers that do not fit a REC_TYPE_NORM record.
573 */
574 if (hdr_opts == 0) {
575 cleanup_out_header(state, header_buf);
576 return;
577 }
578
579 /*
580 * Allow 8-bit type info to override 7-bit type info. XXX Should reuse
581 * the effort that went into MIME header parsing.
582 */
583 hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
584 while (ISSPACE(*hdrval))
585 hdrval++;
586 /* trimblanks(hdrval, 0)[0] = 0; */
587 if (var_auto_8bit_enc_hdr
588 && hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
589 for (cmp = code_map; cmp->name != 0; cmp++) {
590 if (strcasecmp(hdrval, cmp->name) == 0) {
591 if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0)
592 nvtable_update(state->attr, MAIL_ATTR_ENCODING,
593 cmp->encoding);
594 break;
595 }
596 }
597 }
598
599 /*
600 * Copy attachment etc. header blocks without further inspection.
601 */
602 if (header_class != MIME_HDR_PRIMARY) {
603 cleanup_out_header(state, header_buf);
604 return;
605 }
606
607 /*
608 * Known header. Remember that we have seen at least one. Find out what
609 * we should do with this header: delete, count, rewrite. Note that we
610 * should examine headers even when they will be deleted from the output,
611 * because the addresses in those headers might be needed elsewhere.
612 *
613 * XXX 2821: Return-path breakage.
614 *
615 * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a
616 * message it inserts at the beginning of the mail data a return path
617 * line. The return path line preserves the information in the
618 * <reverse-path> from the MAIL command. Here, final delivery means the
619 * message leaves the SMTP world. Normally, this would mean it has been
620 * delivered to the destination user, but in some cases it may be further
621 * processed and transmitted by another mail system.
622 *
623 * And that is what Postfix implements. Delivery agents prepend
624 * Return-Path:. In order to avoid cluttering up the message with
625 * possibly inconsistent Return-Path: information (the sender can change
626 * as the result of mail forwarding or mailing list delivery), Postfix
627 * removes any existing Return-Path: headers.
628 *
629 * RFC 2821 Section 4.4 specifies: A message-originating SMTP system
630 * SHOULD NOT send a message that already contains a Return-path header.
631 * SMTP servers performing a relay function MUST NOT inspect the message
632 * data, and especially not to the extent needed to determine if
633 * Return-path headers are present. SMTP servers making final delivery
634 * MAY remove Return-path headers before adding their own.
635 */
636 else {
637 state->headers_seen |= HDRS_SEEN_MASK(hdr_opts->type);
638 if (hdr_opts->type == HDR_MESSAGE_ID) {
639 ssize_t len;
640
641 msg_info("%s: message-id=%s", state->queue_id, hdrval);
642 if (state->message_id == 0 && (len = balpar(hdrval, "<>")) > 0)
643 /* This Message ID may end up in threaded bounces. */
644 state->message_id = printable(mystrndup(hdrval, len), ' ');
645 }
646 if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
647 msg_info("%s: resent-message-id=%s", state->queue_id, hdrval);
648 if (hdr_opts->type == HDR_RECEIVED) {
649 if (state->hop_count >= var_hopcount_limit) {
650 msg_warn("%s: message rejected: hopcount exceeded",
651 state->queue_id);
652 state->errs |= CLEANUP_STAT_HOPS;
653 }
654 /* Save our Received: header after maybe updating headers above. */
655 if (state->hop_count == 1)
656 argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END);
657 }
658 if (hdr_opts->type == HDR_TLS_REQUIRED && var_tls_required_enable) {
659 char *cp = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
660
661 while (ISSPACE(*cp))
662 cp++;
663 if (strcasecmp(cp, "no") == 0)
664 state->sendopts |= SOPT_REQUIRETLS_HEADER;
665 else
666 msg_warn("ignoring malformed header: '%.100s'",
667 vstring_str(header_buf));
668 }
669 if (CLEANUP_OUT_OK(state)) {
670 if (hdr_opts->flags & HDR_OPT_RR)
671 state->resent = "Resent-";
672 if ((hdr_opts->flags & HDR_OPT_SENDER)
673 && state->hdr_rewrite_context) {
674 cleanup_rewrite_sender(state, hdr_opts, header_buf);
675 } else if ((hdr_opts->flags & HDR_OPT_RECIP)
676 && state->hdr_rewrite_context) {
677 cleanup_rewrite_recip(state, hdr_opts, header_buf);
678 } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
679 cleanup_out_header(state, header_buf);
680 }
681 }
682 }
683 }
684
685 /* get_fullname_hdr_text - helper wrapper */
686
get_fullname_hdr_text(VSTRING * result,const char * raw_name,const char * separator)687 static char *get_fullname_hdr_text(VSTRING *result, const char *raw_name,
688 const char *separator)
689 {
690 VSTRING *sanitized;
691 char *ret;
692
693 if (raw_name == 0 || *raw_name == 0)
694 return (0);
695
696 /*
697 * TODO(wietse) in the ASCII-only path, add support to insert newline
698 * instead of space, to enable header folding with cleanup_fold_header().
699 */
700 sanitized = vstring_alloc(100);
701 if (clean_ascii_cntrl_space(sanitized, raw_name, strlen(raw_name)) == 0) {
702 ret = 0;
703 } else if (allascii(vstring_str(sanitized))) {
704 ret = make_ascii_header_text(result,
705 cleanup_hfrom_format == HFROM_FORMAT_CODE_STD ?
706 HDR_TEXT_FLAG_PHRASE :
707 HDR_TEXT_FLAG_COMMENT,
708 vstring_str(sanitized));
709 } else {
710 ret = rfc2047_encode(result,
711 cleanup_hfrom_format == HFROM_FORMAT_CODE_STD ?
712 RFC2047_HEADER_CONTEXT_PHRASE :
713 RFC2047_HEADER_CONTEXT_COMMENT,
714 var_full_name_encoding_charset,
715 vstring_str(sanitized),
716 VSTRING_LEN(sanitized), separator);
717 }
718 vstring_free(sanitized);
719 return (ret);
720 }
721
722 /* cleanup_header_done_callback - insert missing message headers */
723
cleanup_header_done_callback(void * context)724 static void cleanup_header_done_callback(void *context)
725 {
726 const char *myname = "cleanup_header_done_callback";
727 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
728 char time_stamp[1024]; /* XXX locale dependent? */
729 struct tm *tp;
730 time_t tv;
731
732 /*
733 * XXX Workaround: when we reach the end of headers, mime_state_update()
734 * may execute up to three call-backs before returning to the caller:
735 * head_out(), head_end(), and body_out() or body_end(). As long as
736 * call-backs don't return a result, each call-back has to check for
737 * itself if the previous call-back experienced a problem.
738 */
739 if (CLEANUP_OUT_OK(state) == 0)
740 return;
741
742 /*
743 * Future proofing: the Milter client's header suppression algorithm
744 * assumes that the MTA prepends its own Received: header. This
745 * assumption may be violated after some source-code update. The
746 * following check ensures consistency, at least for local submission.
747 */
748 if (state->hop_count < 1) {
749 msg_warn("%s: message rejected: no Received: header",
750 state->queue_id);
751 state->errs |= CLEANUP_STAT_BAD;
752 return;
753 }
754
755 /*
756 * Add a missing (Resent-)Message-Id: header. The message ID gives the
757 * time in GMT units, plus the local queue ID.
758 *
759 * XXX Message-Id is not a required message header (RFC 822 and RFC 2822).
760 *
761 * XXX It is the queue ID non-inode bits that prevent messages from getting
762 * the same Message-Id within the same second.
763 *
764 * XXX An arbitrary amount of time may pass between the start of the mail
765 * transaction and the creation of a queue file. Since we guarantee queue
766 * ID uniqueness only within a second, we must ensure that the time in
767 * the message ID matches the queue ID creation time, as long as we use
768 * the queue ID in the message ID.
769 *
770 * XXX We log a dummy name=value record so that we (hopefully) don't break
771 * compatibility with existing logfile analyzers, and so that we don't
772 * complicate future code that wants to log more name=value attributes.
773 */
774 if ((state->hdr_rewrite_context || var_always_add_hdrs)
775 && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
776 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID)) == 0) {
777 if (var_long_queue_ids) {
778 vstring_sprintf(state->temp1, "%s@%s",
779 state->queue_id, var_myhostname);
780 } else {
781 tv = state->handle->ctime.tv_sec;
782 tp = gmtime(&tv);
783 strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp);
784 vstring_sprintf(state->temp1, "%s.%s@%s",
785 time_stamp, state->queue_id, var_myhostname);
786 }
787 vstring_sprintf(state->temp2, "%sMessage-Id: <%s>",
788 state->resent, vstring_str(state->temp1));
789 cleanup_out_header(state, state->temp2);
790 msg_info("%s: %smessage-id=<%s>",
791 state->queue_id, *state->resent ? "resent-" : "",
792 vstring_str(state->temp1));
793 state->headers_seen |= HDRS_SEEN_MASK(state->resent[0] ?
794 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID);
795 if (state->resent[0] == 0 && state->message_id == 0)
796 state->message_id = concatenate("<", vstring_str(state->temp1),
797 ">", (char *) 0);
798
799 }
800 if ((state->headers_seen & HDRS_SEEN_MASK(HDR_MESSAGE_ID)) == 0)
801 msg_info("%s: message-id=<>", state->queue_id);
802
803 /*
804 * Add a missing (Resent-)Date: header. The date is in local time units,
805 * with the GMT offset at the end.
806 */
807 if ((state->hdr_rewrite_context || var_always_add_hdrs)
808 && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
809 HDR_RESENT_DATE : HDR_DATE)) == 0) {
810 vstring_sprintf(state->temp2, "%sDate: %s",
811 state->resent, mail_date(state->arrival_time.tv_sec));
812 cleanup_out_header(state, state->temp2);
813 }
814
815 /*
816 * Add a missing (Resent-)From: header.
817 */
818 if ((state->hdr_rewrite_context || var_always_add_hdrs)
819 && (state->headers_seen & HDRS_SEEN_MASK(state->resent[0] ?
820 HDR_RESENT_FROM : HDR_FROM)) == 0) {
821 char *fullname;
822
823 quote_822_local(state->temp1, *state->sender ?
824 state->sender : MAIL_ADDR_MAIL_DAEMON);
825 if (*state->sender != 0
826 && (fullname = get_fullname_hdr_text(state->temp3,
827 state->fullname, "\n")) != 0
828 && *fullname != 0) {
829
830 /*
831 * "From: phrase <addr-spec>".
832 */
833 if (cleanup_hfrom_format == HFROM_FORMAT_CODE_STD) {
834 vstring_sprintf(state->temp2, "%sFrom: %s\n<%s>",
835 state->resent, fullname,
836 vstring_str(state->temp1));
837 }
838
839 /*
840 * "From: addr-spec (ctext)". This is the obsolete form.
841 */
842 else {
843 vstring_sprintf(state->temp2, "%sFrom: %s\n(%s)",
844 state->resent, vstring_str(state->temp1),
845 fullname);
846 }
847 }
848
849 /*
850 * "From: addr-spec". This is the form in the absence of full name
851 * information, also used for mail from mailer-daemon.
852 */
853 else {
854 vstring_sprintf(state->temp2, "%sFrom: %s",
855 state->resent, vstring_str(state->temp1));
856 }
857 cleanup_fold_header(state, state->temp2);
858 }
859
860 /*
861 * XXX 2821: Appendix B: The return address in the MAIL command SHOULD,
862 * if possible, be derived from the system's identity for the submitting
863 * (local) user, and the "From:" header field otherwise. If there is a
864 * system identity available, it SHOULD also be copied to the Sender
865 * header field if it is different from the address in the From header
866 * field. (Any Sender field that was already there SHOULD be removed.)
867 * Similar wording appears in RFC 2822 section 3.6.2.
868 *
869 * Postfix presently does not insert a Sender: header if envelope and From:
870 * address differ. Older Postfix versions assumed that the envelope
871 * sender address specifies the system identity and inserted Sender:
872 * whenever envelope and From: differed. This was wrong with relayed
873 * mail, and was often not even desirable with original submissions.
874 *
875 * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can
876 * contain multiple addresses. If this is the case, then a Sender: header
877 * must be provided with a single address.
878 *
879 * Postfix does not count the number of addresses in a From: header
880 * (although doing so is trivial, once the address is parsed).
881 */
882
883 /*
884 * Add a missing destination header.
885 */
886 #define VISIBLE_RCPT (HDRS_SEEN_MASK(HDR_TO) \
887 | HDRS_SEEN_MASK(HDR_RESENT_TO) \
888 | HDRS_SEEN_MASK(HDR_CC) \
889 | HDRS_SEEN_MASK(HDR_RESENT_CC))
890
891 if ((state->hdr_rewrite_context || var_always_add_hdrs)
892 && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) {
893 if (!is_header(var_rcpt_witheld)) {
894 msg_warn("bad %s header text \"%s\" -- "
895 "need \"headername: headervalue\"",
896 VAR_RCPT_WITHELD, var_rcpt_witheld);
897 } else {
898 cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld);
899 }
900 }
901
902 /*
903 * Place a dummy PTR record right after the last header so that we can
904 * append headers without having to worry about clobbering the
905 * end-of-content marker.
906 */
907 if (state->milters || cleanup_milters) {
908 if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0)
909 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
910 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
911 if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0)
912 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
913 state->body_offset = state->append_hdr_pt_target;
914 }
915 }
916
917 /* cleanup_body_callback - output one body record */
918
cleanup_body_callback(void * context,int type,const char * buf,ssize_t len,off_t offset)919 static void cleanup_body_callback(void *context, int type,
920 const char *buf, ssize_t len,
921 off_t offset)
922 {
923 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
924
925 /*
926 * XXX Workaround: when we reach the end of headers, mime_state_update()
927 * may execute up to three call-backs before returning to the caller:
928 * head_out(), head_end(), and body_out() or body_end(). As long as
929 * call-backs don't return a result, each call-back has to check for
930 * itself if the previous call-back experienced a problem.
931 */
932 if (CLEANUP_OUT_OK(state) == 0)
933 return;
934
935 /*
936 * Crude message body content filter for emergencies. This code has
937 * several problems: it sees one line at a time; it looks at long lines
938 * only in chunks of line_length_limit (2048) characters; it is easily
939 * bypassed with encodings and other tricks.
940 */
941 if ((state->flags & CLEANUP_FLAG_FILTER)
942 && cleanup_body_checks
943 && (var_body_check_len == 0 || offset < var_body_check_len)) {
944 const char *value;
945
946 if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
947 const char *result;
948
949 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
950 buf, value, VAR_BODY_CHECKS))
951 == CLEANUP_ACT_DROP) {
952 return;
953 } else if (result != buf) {
954 cleanup_out(state, type, result, strlen(result));
955 myfree((void *) result);
956 return;
957 }
958 } else if (cleanup_body_checks->error) {
959 msg_warn("%s: %s map lookup problem -- "
960 "message not accepted, try again later",
961 state->queue_id, cleanup_body_checks->title);
962 state->errs |= CLEANUP_STAT_WRITE;
963 }
964 }
965 cleanup_out(state, type, buf, len);
966 }
967
968 /* cleanup_message_headerbody - process message content, header and body */
969
cleanup_message_headerbody(CLEANUP_STATE * state,int type,const char * buf,ssize_t len)970 static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
971 const char *buf, ssize_t len)
972 {
973 const char *myname = "cleanup_message_headerbody";
974 const MIME_STATE_DETAIL *detail;
975 const char *cp;
976 char *dst;
977
978 /*
979 * Replace each stray CR or LF with one space. These are not allowed in
980 * SMTP, and can be used to enable outbound (remote) SMTP smuggling.
981 * Replacing these early ensures that our later DKIM etc. signature will
982 * not be invalidated. Besides preventing SMTP smuggling, replacing stray
983 * <CR> or <LF> ensures that the result of signature validation by a
984 * later mail system will not depend on how that mail system handles
985 * those stray characters in an implementation-dependent manner.
986 *
987 * The input length is not changed, therefore it is safe to overwrite the
988 * input.
989 */
990 if (var_cleanup_mask_stray_cr_lf)
991 for (dst = (char *) buf; dst < buf + len; dst++)
992 if (*dst == '\r' || *dst == '\n')
993 *dst = ' ';
994
995 /*
996 * Reject unwanted characters.
997 *
998 * XXX Possible optimization: simplify the loop when the "reject" set
999 * contains only one character.
1000 */
1001 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) {
1002 for (cp = buf; cp < buf + len; cp++) {
1003 if (memchr(vstring_str(cleanup_reject_chars),
1004 *(const unsigned char *) cp,
1005 VSTRING_LEN(cleanup_reject_chars))) {
1006 cleanup_act(state, CLEANUP_ACT_CTXT_ANY,
1007 buf, "REJECT disallowed character",
1008 "character reject");
1009 return;
1010 }
1011 }
1012 }
1013
1014 /*
1015 * Strip unwanted characters. Don't overwrite the input.
1016 *
1017 * XXX Possible space+time optimization: use a bitset.
1018 *
1019 * XXX Possible optimization: simplify the loop when the "strip" set
1020 * contains only one character.
1021 *
1022 * XXX Possible optimization: copy the input only if we really have to.
1023 */
1024 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) {
1025 VSTRING_RESET(state->stripped_buf);
1026 VSTRING_SPACE(state->stripped_buf, len + 1);
1027 dst = vstring_str(state->stripped_buf);
1028 for (cp = buf; cp < buf + len; cp++)
1029 if (!memchr(vstring_str(cleanup_strip_chars),
1030 *(const unsigned char *) cp,
1031 VSTRING_LEN(cleanup_strip_chars)))
1032 *dst++ = *cp;
1033 *dst = 0;
1034 buf = vstring_str(state->stripped_buf);
1035 len = dst - buf;
1036 }
1037
1038 /*
1039 * Copy text record to the output.
1040 */
1041 if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) {
1042 state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
1043 }
1044
1045 /*
1046 * If we have reached the end of the message content segment, record the
1047 * current file position so we can compute the message size lateron.
1048 */
1049 else if (type == REC_TYPE_XTRA) {
1050 state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
1051 if (state->milters || cleanup_milters)
1052 /* Make room for body modification. */
1053 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
1054 /* Ignore header truncation after primary message headers. */
1055 state->mime_errs &= ~MIME_ERR_TRUNC_HEADER;
1056 if (state->mime_errs && state->reason == 0) {
1057 state->errs |= CLEANUP_STAT_CONT;
1058 detail = mime_state_detail(state->mime_errs);
1059 state->reason = dsn_prepend(detail->dsn, detail->text);
1060 }
1061 state->mime_state = mime_state_free(state->mime_state);
1062 if ((state->xtra_offset = vstream_ftell(state->dst)) < 0)
1063 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
1064 state->cont_length = state->xtra_offset - state->data_offset;
1065 state->action = cleanup_extracted;
1066 }
1067
1068 /*
1069 * This should never happen.
1070 */
1071 else {
1072 msg_warn("%s: message rejected: "
1073 "unexpected record type %d in message content", myname, type);
1074 state->errs |= CLEANUP_STAT_BAD;
1075 }
1076 }
1077
1078 /* cleanup_mime_error_callback - error report call-back routine */
1079
cleanup_mime_error_callback(void * context,int err_code,const char * text,ssize_t len)1080 static void cleanup_mime_error_callback(void *context, int err_code,
1081 const char *text, ssize_t len)
1082 {
1083 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1084 const char *origin;
1085
1086 /*
1087 * Message header too large errors are handled after the end of the
1088 * primary message headers.
1089 */
1090 if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) {
1091 if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
1092 origin = MAIL_ATTR_ORG_NONE;
1093 #define TEXT_LEN (len < 100 ? (int) len : 100)
1094 msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>",
1095 state->queue_id, mime_state_error(err_code), TEXT_LEN, text,
1096 origin, info_log_addr_form_sender(state->sender),
1097 info_log_addr_form_recipient(state->recip ?
1098 state->recip : "unknown"));
1099 }
1100 }
1101
1102 /* cleanup_message - initialize message content segment */
1103
cleanup_message(CLEANUP_STATE * state,int type,const char * buf,ssize_t len)1104 void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len)
1105 {
1106 const char *myname = "cleanup_message";
1107 int mime_options;
1108
1109 /*
1110 * Write the start-of-content segment marker.
1111 */
1112 cleanup_out_string(state, REC_TYPE_MESG, "");
1113 if ((state->data_offset = vstream_ftell(state->dst)) < 0)
1114 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
1115
1116 /*
1117 * Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables
1118 * special processing of Content-Type: headers, and thus, causes all text
1119 * after the primary headers to be treated as the message body.
1120 */
1121 mime_options = 0;
1122 if (var_disable_mime_input) {
1123 if (var_force_mime_iconv)
1124 msg_fatal("do not specify both %s=yes and %s=yes",
1125 VAR_DISABLE_MIME_INPUT, VAR_FORCE_MIME_ICONV);
1126 mime_options |= MIME_OPT_DISABLE_MIME;
1127 } else {
1128 /* Turn off content checks if bouncing or forwarding mail. */
1129 if (state->flags & CLEANUP_FLAG_FILTER) {
1130 if (var_strict_8bitmime || var_strict_7bit_hdrs)
1131 mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER;
1132 if (var_strict_8bitmime || var_strict_8bit_body)
1133 mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY;
1134 if (var_strict_encoding)
1135 mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN;
1136 if (var_strict_8bitmime || var_strict_7bit_hdrs
1137 || var_strict_8bit_body || var_strict_encoding
1138 || *var_header_checks || *var_mimehdr_checks
1139 || *var_nesthdr_checks)
1140 mime_options |= MIME_OPT_REPORT_NESTING;
1141 }
1142 if (var_force_mime_iconv)
1143 mime_options |= MIME_OPT_DOWNGRADE;
1144 }
1145 state->mime_state = mime_state_alloc(mime_options,
1146 cleanup_header_callback,
1147 cleanup_header_done_callback,
1148 cleanup_body_callback,
1149 (MIME_STATE_ANY_END) 0,
1150 cleanup_mime_error_callback,
1151 (void *) state);
1152
1153 /*
1154 * XXX Workaround: truncate a long message header so that we don't exceed
1155 * the default Sendmail libmilter request size limit of 65535.
1156 */
1157 #define KLUDGE_HEADER_LIMIT 60000
1158 if ((cleanup_milters || state->milters)
1159 && var_header_limit > KLUDGE_HEADER_LIMIT)
1160 var_header_limit = KLUDGE_HEADER_LIMIT;
1161
1162 /*
1163 * Pass control to the header processing routine.
1164 */
1165 state->action = cleanup_message_headerbody;
1166 cleanup_message_headerbody(state, type, buf, len);
1167 }
1168