Expand PMF_FN_* macros.
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / cleanup / cleanup_message.c
blob3524f06913e9cf2f1d801eeac97c05bca9985817
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* cleanup_message 3
6 /* SUMMARY
7 /* process message segment
8 /* SYNOPSIS
9 /* #include "cleanup.h"
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.
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 /*--*/
46 /* System library. */
48 #include <sys_defs.h>
49 #include <ctype.h>
50 #include <string.h>
51 #include <time.h>
52 #include <unistd.h>
54 #ifdef STRCASECMP_IN_STRINGS_H
55 #include <strings.h>
56 #endif
58 /* Utility library. */
60 #include <msg.h>
61 #include <vstring.h>
62 #include <vstream.h>
63 #include <argv.h>
64 #include <split_at.h>
65 #include <mymalloc.h>
66 #include <stringops.h>
67 #include <nvtable.h>
69 /* Global library. */
71 #include <record.h>
72 #include <rec_type.h>
73 #include <cleanup_user.h>
74 #include <tok822.h>
75 #include <header_opts.h>
76 #include <quote_822_local.h>
77 #include <mail_params.h>
78 #include <mail_date.h>
79 #include <mail_addr.h>
80 #include <is_header.h>
81 #include <ext_prop.h>
82 #include <mail_proto.h>
83 #include <mime_state.h>
84 #include <lex_822.h>
85 #include <dsn_util.h>
86 #include <conv_time.h>
88 /* Application-specific. */
90 #include "cleanup.h"
92 /* cleanup_fold_header - wrap address list header */
94 static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf)
96 char *start_line = vstring_str(header_buf);
97 char *end_line;
98 char *next_line;
99 char *line;
102 * A rewritten address list contains one address per line. The code below
103 * replaces newlines by spaces, to fit as many addresses on a line as
104 * possible (without rearranging the order of addresses). Prepending
105 * white space to the beginning of lines is delegated to the output
106 * routine.
108 for (line = start_line; line != 0; line = next_line) {
109 end_line = line + strcspn(line, "\n");
110 if (line > start_line) {
111 if (end_line - start_line < 70) { /* TAB counts as one */
112 line[-1] = ' ';
113 } else {
114 start_line = line;
117 next_line = *end_line ? end_line + 1 : 0;
119 cleanup_out_header(state, header_buf);
122 /* cleanup_extract_internal - save unquoted copy of extracted address */
124 static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr)
128 * A little routine to stash away a copy of an address that we extracted
129 * from a message header line.
131 tok822_internalize(buffer, addr->head, TOK822_STR_DEFL);
132 return (mystrdup(vstring_str(buffer)));
135 /* cleanup_rewrite_sender - sender address rewriting */
137 static void cleanup_rewrite_sender(CLEANUP_STATE *state,
138 const HEADER_OPTS *hdr_opts,
139 VSTRING *header_buf)
141 TOK822 *tree;
142 TOK822 **addr_list;
143 TOK822 **tpp;
144 int did_rewrite = 0;
146 if (msg_verbose)
147 msg_info("rewrite_sender: %s", hdr_opts->name);
150 * Parse the header line, rewrite each address found, and regenerate the
151 * header line. Finally, pipe the result through the header line folding
152 * routine.
154 tree = tok822_parse_limit(vstring_str(header_buf)
155 + strlen(hdr_opts->name) + 1,
156 var_token_limit);
157 addr_list = tok822_grep(tree, TOK822_ADDR);
158 for (tpp = addr_list; *tpp; tpp++) {
159 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
160 if (state->flags & CLEANUP_FLAG_MAP_OK) {
161 if (cleanup_send_canon_maps
162 && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
163 did_rewrite |=
164 cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps,
165 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
166 if (cleanup_comm_canon_maps
167 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
168 did_rewrite |=
169 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
170 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
171 if (cleanup_masq_domains
172 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM))
173 did_rewrite |=
174 cleanup_masquerade_tree(*tpp, cleanup_masq_domains);
177 if (did_rewrite) {
178 vstring_truncate(header_buf, strlen(hdr_opts->name));
179 vstring_strcat(header_buf, ": ");
180 tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
182 myfree((char *) addr_list);
183 tok822_free_tree(tree);
184 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
185 if (did_rewrite)
186 cleanup_fold_header(state, header_buf);
187 else
188 cleanup_out_header(state, header_buf);
192 /* cleanup_rewrite_recip - recipient address rewriting */
194 static void cleanup_rewrite_recip(CLEANUP_STATE *state,
195 const HEADER_OPTS *hdr_opts,
196 VSTRING *header_buf)
198 TOK822 *tree;
199 TOK822 **addr_list;
200 TOK822 **tpp;
201 int did_rewrite = 0;
203 if (msg_verbose)
204 msg_info("rewrite_recip: %s", hdr_opts->name);
207 * Parse the header line, rewrite each address found, and regenerate the
208 * header line. Finally, pipe the result through the header line folding
209 * routine.
211 tree = tok822_parse_limit(vstring_str(header_buf)
212 + strlen(hdr_opts->name) + 1,
213 var_token_limit);
214 addr_list = tok822_grep(tree, TOK822_ADDR);
215 for (tpp = addr_list; *tpp; tpp++) {
216 did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
217 if (state->flags & CLEANUP_FLAG_MAP_OK) {
218 if (cleanup_rcpt_canon_maps
219 && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
220 did_rewrite |=
221 cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps,
222 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
223 if (cleanup_comm_canon_maps
224 && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
225 did_rewrite |=
226 cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
227 cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
228 if (cleanup_masq_domains
229 && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT))
230 did_rewrite |=
231 cleanup_masquerade_tree(*tpp, cleanup_masq_domains);
234 if (did_rewrite) {
235 vstring_truncate(header_buf, strlen(hdr_opts->name));
236 vstring_strcat(header_buf, ": ");
237 tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
239 myfree((char *) addr_list);
240 tok822_free_tree(tree);
241 if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
242 if (did_rewrite)
243 cleanup_fold_header(state, header_buf);
244 else
245 cleanup_out_header(state, header_buf);
249 /* cleanup_act_log - log action with context */
251 static void cleanup_act_log(CLEANUP_STATE *state,
252 const char *action, const char *class,
253 const char *content, const char *text)
255 const char *attr;
257 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
258 attr = "unknown";
259 vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;",
260 state->queue_id, action, class, content, attr);
261 if (state->sender)
262 vstring_sprintf_append(state->temp1, " from=<%s>", state->sender);
263 if (state->recip)
264 vstring_sprintf_append(state->temp1, " to=<%s>", state->recip);
265 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
266 vstring_sprintf_append(state->temp1, " proto=%s", attr);
267 if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
268 vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
269 if (text && *text)
270 vstring_sprintf_append(state->temp1, ": %s", text);
271 msg_info("%s", vstring_str(state->temp1));
274 #define CLEANUP_ACT_CTXT_HEADER "header"
275 #define CLEANUP_ACT_CTXT_BODY "body"
276 #define CLEANUP_ACT_CTXT_ANY "content"
278 /* cleanup_act - act upon a header/body match */
280 static const char *cleanup_act(CLEANUP_STATE *state, char *context,
281 const char *buf, const char *value,
282 const char *map_class)
284 const char *optional_text = value + strcspn(value, " \t");
285 int command_len = optional_text - value;
287 #ifdef DELAY_ACTION
288 int defer_delay;
290 #endif
292 while (*optional_text && ISSPACE(*optional_text))
293 optional_text++;
295 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
296 #define CLEANUP_ACT_DROP 0
299 * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
300 * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
301 * queue record processing, and prevents bounces from being sent.
303 if (STREQUAL(value, "REJECT", command_len)) {
304 const CLEANUP_STAT_DETAIL *detail;
306 if (state->reason)
307 myfree(state->reason);
308 if (*optional_text) {
309 state->reason = dsn_prepend("5.7.1", optional_text);
310 if (*state->reason != '4' && *state->reason != '5') {
311 msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
312 optional_text);
313 *state->reason = '4';
315 } else {
316 detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
317 state->reason = dsn_prepend(detail->dsn, detail->text);
319 if (*state->reason == '4')
320 state->errs |= CLEANUP_STAT_DEFER;
321 else
322 state->errs |= CLEANUP_STAT_CONT;
323 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
324 cleanup_act_log(state, "reject", context, buf, state->reason);
325 return (buf);
327 if (STREQUAL(value, "WARN", command_len)) {
328 cleanup_act_log(state, "warning", context, buf, optional_text);
329 return (buf);
331 if (STREQUAL(value, "FILTER", command_len)) {
332 if (*optional_text == 0) {
333 msg_warn("missing FILTER command argument in %s map", map_class);
334 } else if (strchr(optional_text, ':') == 0) {
335 msg_warn("bad FILTER command %s in %s -- "
336 "need transport:destination",
337 optional_text, map_class);
338 } else {
339 if (state->filter)
340 myfree(state->filter);
341 state->filter = mystrdup(optional_text);
342 cleanup_act_log(state, "filter", context, buf, optional_text);
344 return (buf);
346 if (STREQUAL(value, "DISCARD", command_len)) {
347 cleanup_act_log(state, "discard", context, buf, optional_text);
348 state->flags |= CLEANUP_FLAG_DISCARD;
349 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
350 return (buf);
352 if (STREQUAL(value, "HOLD", command_len)) {
353 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
354 cleanup_act_log(state, "hold", context, buf, optional_text);
355 state->flags |= CLEANUP_FLAG_HOLD;
357 return (buf);
361 * The DELAY feature is disabled because it has too many problems. 1) It
362 * does not work on some remote file systems; 2) mail will be delivered
363 * anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs
364 * down the deferred queue scan with huge amounts of useless disk I/O
365 * operations.
367 #ifdef DELAY_ACTION
368 if (STREQUAL(value, "DELAY", command_len)) {
369 if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
370 if (*optional_text == 0) {
371 msg_warn("missing DELAY argument in %s map", map_class);
372 } else if (conv_time(optional_text, &defer_delay, 's') == 0) {
373 msg_warn("ignoring bad DELAY argument %s in %s map",
374 optional_text, map_class);
375 } else {
376 cleanup_act_log(state, "delay", context, buf, optional_text);
377 state->defer_delay = defer_delay;
380 return (buf);
382 #endif
383 if (STREQUAL(value, "PREPEND", command_len)) {
384 if (*optional_text == 0) {
385 msg_warn("PREPEND action without text in %s map", map_class);
386 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
387 && !is_header(optional_text)) {
388 msg_warn("bad PREPEND header text \"%s\" in %s map -- "
389 "need \"headername: headervalue\"",
390 optional_text, map_class);
391 } else {
392 cleanup_act_log(state, "prepend", context, buf, optional_text);
393 cleanup_out_string(state, REC_TYPE_NORM, optional_text);
395 return (buf);
397 if (STREQUAL(value, "REPLACE", command_len)) {
398 if (*optional_text == 0) {
399 msg_warn("REPLACE action without text in %s map", map_class);
400 return (buf);
401 } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
402 && !is_header(optional_text)) {
403 msg_warn("bad REPLACE header text \"%s\" in %s map -- "
404 "need \"headername: headervalue\"",
405 optional_text, map_class);
406 return (buf);
407 } else {
408 cleanup_act_log(state, "replace", context, buf, optional_text);
409 return (mystrdup(optional_text));
412 if (STREQUAL(value, "REDIRECT", command_len)) {
413 if (strchr(optional_text, '@') == 0) {
414 msg_warn("bad REDIRECT target \"%s\" in %s map -- "
415 "need user@domain",
416 optional_text, map_class);
417 } else {
418 if (state->redirect)
419 myfree(state->redirect);
420 state->redirect = mystrdup(optional_text);
421 cleanup_act_log(state, "redirect", context, buf, optional_text);
422 state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
424 return (buf);
426 /* Allow and ignore optional text after the action. */
428 if (STREQUAL(value, "IGNORE", command_len))
429 return (CLEANUP_ACT_DROP);
431 if (STREQUAL(value, "DUNNO", command_len)) /* preferred */
432 return (buf);
434 if (STREQUAL(value, "OK", command_len)) /* compat */
435 return (buf);
437 msg_warn("unknown command in %s map: %s", map_class, value);
438 return (buf);
441 /* cleanup_header_callback - process one complete header line */
443 static void cleanup_header_callback(void *context, int header_class,
444 const HEADER_OPTS *hdr_opts,
445 VSTRING *header_buf,
446 off_t unused_offset)
448 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
449 const char *myname = "cleanup_header_callback";
450 char *hdrval;
451 struct code_map {
452 const char *name;
453 const char *encoding;
455 static struct code_map code_map[] = { /* RFC 2045 */
456 "7bit", MAIL_ATTR_ENC_7BIT,
457 "8bit", MAIL_ATTR_ENC_8BIT,
458 "binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */
459 "quoted-printable", MAIL_ATTR_ENC_7BIT,
460 "base64", MAIL_ATTR_ENC_7BIT,
463 struct code_map *cmp;
464 MAPS *checks;
465 const char *map_class;
467 if (msg_verbose)
468 msg_info("%s: '%.200s'", myname, vstring_str(header_buf));
471 * Crude header filtering. This stops malware that isn't sophisticated
472 * enough to use fancy header encodings.
474 #define CHECK(class, maps, var_name) \
475 (header_class == class && (map_class = var_name, checks = maps) != 0)
477 if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
478 header_class = MIME_HDR_MULTIPART;
480 if ((state->flags & CLEANUP_FLAG_FILTER)
481 && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS)
482 || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS)
483 || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) {
484 char *header = vstring_str(header_buf);
485 const char *value;
487 if ((value = maps_find(checks, header, 0)) != 0) {
488 const char *result;
490 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
491 header, value, map_class))
492 == CLEANUP_ACT_DROP) {
493 return;
494 } else if (result != header) {
495 vstring_strcpy(header_buf, result);
496 hdr_opts = header_opts_find(result);
497 myfree((char *) result);
503 * If this is an "unknown" header, just copy it to the output without
504 * even bothering to fold long lines. cleanup_out() will split long
505 * headers that do not fit a REC_TYPE_NORM record.
507 if (hdr_opts == 0) {
508 cleanup_out_header(state, header_buf);
509 return;
513 * Allow 8-bit type info to override 7-bit type info. XXX Should reuse
514 * the effort that went into MIME header parsing.
516 hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
517 while (ISSPACE(*hdrval))
518 hdrval++;
519 /* trimblanks(hdrval, 0)[0] = 0; */
520 if (var_auto_8bit_enc_hdr
521 && hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
522 for (cmp = code_map; cmp->name != 0; cmp++) {
523 if (strcasecmp(hdrval, cmp->name) == 0) {
524 if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0)
525 nvtable_update(state->attr, MAIL_ATTR_ENCODING,
526 cmp->encoding);
527 break;
533 * Copy attachment etc. header blocks without further inspection.
535 if (header_class != MIME_HDR_PRIMARY) {
536 cleanup_out_header(state, header_buf);
537 return;
541 * Known header. Remember that we have seen at least one. Find out what
542 * we should do with this header: delete, count, rewrite. Note that we
543 * should examine headers even when they will be deleted from the output,
544 * because the addresses in those headers might be needed elsewhere.
546 * XXX 2821: Return-path breakage.
548 * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a
549 * message it inserts at the beginning of the mail data a return path
550 * line. The return path line preserves the information in the
551 * <reverse-path> from the MAIL command. Here, final delivery means the
552 * message leaves the SMTP world. Normally, this would mean it has been
553 * delivered to the destination user, but in some cases it may be further
554 * processed and transmitted by another mail system.
556 * And that is what Postfix implements. Delivery agents prepend
557 * Return-Path:. In order to avoid cluttering up the message with
558 * possibly inconsistent Return-Path: information (the sender can change
559 * as the result of mail forwarding or mailing list delivery), Postfix
560 * removes any existing Return-Path: headers.
562 * RFC 2821 Section 4.4 specifies: A message-originating SMTP system
563 * SHOULD NOT send a message that already contains a Return-path header.
564 * SMTP servers performing a relay function MUST NOT inspect the message
565 * data, and especially not to the extent needed to determine if
566 * Return-path headers are present. SMTP servers making final delivery
567 * MAY remove Return-path headers before adding their own.
569 else {
570 state->headers_seen |= (1 << hdr_opts->type);
571 if (hdr_opts->type == HDR_MESSAGE_ID)
572 msg_info("%s: message-id=%s", state->queue_id, hdrval);
573 if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
574 msg_info("%s: resent-message-id=%s", state->queue_id, hdrval);
575 if (hdr_opts->type == HDR_RECEIVED)
576 if (++state->hop_count >= var_hopcount_limit)
577 state->errs |= CLEANUP_STAT_HOPS;
578 if (CLEANUP_OUT_OK(state)) {
579 if (hdr_opts->flags & HDR_OPT_RR)
580 state->resent = "Resent-";
581 if ((hdr_opts->flags & HDR_OPT_SENDER)
582 && state->hdr_rewrite_context) {
583 cleanup_rewrite_sender(state, hdr_opts, header_buf);
584 } else if ((hdr_opts->flags & HDR_OPT_RECIP)
585 && state->hdr_rewrite_context) {
586 cleanup_rewrite_recip(state, hdr_opts, header_buf);
587 } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
588 cleanup_out_header(state, header_buf);
594 /* cleanup_header_done_callback - insert missing message headers */
596 static void cleanup_header_done_callback(void *context)
598 const char *myname = "cleanup_header_done_callback";
599 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
600 char time_stamp[1024]; /* XXX locale dependent? */
601 struct tm *tp;
602 TOK822 *token;
603 time_t tv;
606 * XXX Workaround: when we reach the end of headers, mime_state_update()
607 * may execute up to three call-backs before returning to the caller:
608 * head_out(), head_end(), and body_out() or body_end(). As long as
609 * call-backs don't return a result, each call-back has to check for
610 * itself if the previous call-back experienced a problem.
612 if (CLEANUP_OUT_OK(state) == 0)
613 return;
616 * Add a missing (Resent-)Message-Id: header. The message ID gives the
617 * time in GMT units, plus the local queue ID.
619 * XXX Message-Id is not a required message header (RFC 822 and RFC 2822).
621 * XXX It is the queue ID non-inode bits that prevent messages from getting
622 * the same Message-Id within the same second.
624 * XXX An arbitrary amount of time may pass between the start of the mail
625 * transaction and the creation of a queue file. Since we guarantee queue
626 * ID uniqueness only within a second, we must ensure that the time in
627 * the message ID matches the queue ID creation time, as long as we use
628 * the queue ID in the message ID.
630 * XXX We log a dummy name=value record so that we (hopefully) don't break
631 * compatibility with existing logfile analyzers, and so that we don't
632 * complicate future code that wants to log more name=value attributes.
634 if ((state->hdr_rewrite_context || var_always_add_hdrs)
635 && (state->headers_seen & (1 << (state->resent[0] ?
636 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
637 tv = state->handle->ctime.tv_sec;
638 tp = gmtime(&tv);
639 strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp);
640 cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s.%s@%s>",
641 state->resent, time_stamp, state->queue_id, var_myhostname);
642 msg_info("%s: %smessage-id=<%s.%s@%s>",
643 state->queue_id, *state->resent ? "resent-" : "",
644 time_stamp, state->queue_id, var_myhostname);
645 state->headers_seen |= (1 << (state->resent[0] ?
646 HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID));
648 if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0)
649 msg_info("%s: message-id=<>", state->queue_id);
652 * Add a missing (Resent-)Date: header. The date is in local time units,
653 * with the GMT offset at the end.
655 if ((state->hdr_rewrite_context || var_always_add_hdrs)
656 && (state->headers_seen & (1 << (state->resent[0] ?
657 HDR_RESENT_DATE : HDR_DATE))) == 0) {
658 cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s",
659 state->resent, mail_date(state->arrival_time.tv_sec));
663 * Add a missing (Resent-)From: header.
665 if ((state->hdr_rewrite_context || var_always_add_hdrs)
666 && (state->headers_seen & (1 << (state->resent[0] ?
667 HDR_RESENT_FROM : HDR_FROM))) == 0) {
668 quote_822_local(state->temp1, *state->sender ?
669 state->sender : MAIL_ADDR_MAIL_DAEMON);
670 vstring_sprintf(state->temp2, "%sFrom: %s",
671 state->resent, vstring_str(state->temp1));
672 if (*state->sender && state->fullname && *state->fullname) {
673 vstring_sprintf(state->temp1, "(%s)", state->fullname);
674 token = tok822_parse(vstring_str(state->temp1));
675 vstring_strcat(state->temp2, " ");
676 tok822_externalize(state->temp2, token, TOK822_STR_NONE);
677 tok822_free_tree(token);
679 CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2);
683 * XXX 2821: Appendix B: The return address in the MAIL command SHOULD,
684 * if possible, be derived from the system's identity for the submitting
685 * (local) user, and the "From:" header field otherwise. If there is a
686 * system identity available, it SHOULD also be copied to the Sender
687 * header field if it is different from the address in the From header
688 * field. (Any Sender field that was already there SHOULD be removed.)
689 * Similar wording appears in RFC 2822 section 3.6.2.
691 * Postfix presently does not insert a Sender: header if envelope and From:
692 * address differ. Older Postfix versions assumed that the envelope
693 * sender address specifies the system identity and inserted Sender:
694 * whenever envelope and From: differed. This was wrong with relayed
695 * mail, and was often not even desirable with original submissions.
697 * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can
698 * contain multiple addresses. If this is the case, then a Sender: header
699 * must be provided with a single address.
701 * Postfix does not count the number of addresses in a From: header
702 * (although doing so is trivial, once the address is parsed).
706 * Add a missing destination header.
708 #define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \
709 | (1 << HDR_CC) | (1 << HDR_RESENT_CC))
711 if ((state->hdr_rewrite_context || var_always_add_hdrs)
712 && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) {
713 if (!is_header(var_rcpt_witheld)) {
714 msg_warn("bad %s header text \"%s\" -- "
715 "need \"headername: headervalue\"",
716 VAR_RCPT_WITHELD, var_rcpt_witheld);
717 } else {
718 cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld);
723 * Place a dummy PTR record right after the last header so that we can
724 * append headers without having to worry about clobbering the
725 * end-of-content marker.
727 if (state->milters || cleanup_milters) {
728 if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0)
729 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
730 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
731 if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0)
732 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
733 state->body_offset = state->append_hdr_pt_target;
737 /* cleanup_body_callback - output one body record */
739 static void cleanup_body_callback(void *context, int type,
740 const char *buf, ssize_t len,
741 off_t offset)
743 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
746 * XXX Workaround: when we reach the end of headers, mime_state_update()
747 * may execute up to three call-backs before returning to the caller:
748 * head_out(), head_end(), and body_out() or body_end(). As long as
749 * call-backs don't return a result, each call-back has to check for
750 * itself if the previous call-back experienced a problem.
752 if (CLEANUP_OUT_OK(state) == 0)
753 return;
756 * Crude message body content filter for emergencies. This code has
757 * several problems: it sees one line at a time; it looks at long lines
758 * only in chunks of line_length_limit (2048) characters; it is easily
759 * bypassed with encodings and other tricks.
761 if ((state->flags & CLEANUP_FLAG_FILTER)
762 && cleanup_body_checks
763 && (var_body_check_len == 0 || offset < var_body_check_len)) {
764 const char *value;
766 if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
767 const char *result;
769 if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
770 buf, value, VAR_BODY_CHECKS))
771 == CLEANUP_ACT_DROP) {
772 return;
773 } else if (result != buf) {
774 cleanup_out(state, type, result, strlen(result));
775 myfree((char *) result);
776 return;
780 cleanup_out(state, type, buf, len);
783 /* cleanup_message_headerbody - process message content, header and body */
785 static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
786 const char *buf, ssize_t len)
788 const char *myname = "cleanup_message_headerbody";
789 const MIME_STATE_DETAIL *detail;
790 const char *cp;
791 char *dst;
794 * Reject unwanted characters.
796 * XXX Possible optimization: simplify the loop when the "reject" set
797 * contains only one character.
799 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) {
800 for (cp = buf; cp < buf + len; cp++) {
801 if (memchr(vstring_str(cleanup_reject_chars),
802 *(const unsigned char *) cp,
803 VSTRING_LEN(cleanup_reject_chars))) {
804 cleanup_act(state, CLEANUP_ACT_CTXT_ANY,
805 buf, "REJECT disallowed character",
806 "character reject");
807 return;
813 * Strip unwanted characters. Don't overwrite the input.
815 * XXX Possible optimization: simplify the loop when the "strip" set
816 * contains only one character.
818 * XXX Possible optimization: copy the input only if we really have to.
820 if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) {
821 VSTRING_RESET(state->stripped_buf);
822 VSTRING_SPACE(state->stripped_buf, len + 1);
823 dst = vstring_str(state->stripped_buf);
824 for (cp = buf; cp < buf + len; cp++)
825 if (!memchr(vstring_str(cleanup_strip_chars),
826 *(const unsigned char *) cp,
827 VSTRING_LEN(cleanup_strip_chars)))
828 *dst++ = *cp;
829 *dst = 0;
830 buf = vstring_str(state->stripped_buf);
831 len = dst - buf;
835 * Copy text record to the output.
837 if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) {
838 state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
842 * If we have reached the end of the message content segment, record the
843 * current file position so we can compute the message size lateron.
845 else if (type == REC_TYPE_XTRA) {
846 state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
847 if (state->milters || cleanup_milters)
848 /* Make room for body modification. */
849 cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
850 /* Ignore header truncation after primary message headers. */
851 state->mime_errs &= ~MIME_ERR_TRUNC_HEADER;
852 if (state->mime_errs && state->reason == 0) {
853 state->errs |= CLEANUP_STAT_CONT;
854 detail = mime_state_detail(state->mime_errs);
855 state->reason = dsn_prepend(detail->dsn, detail->text);
857 state->mime_state = mime_state_free(state->mime_state);
858 if ((state->xtra_offset = vstream_ftell(state->dst)) < 0)
859 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
860 state->cont_length = state->xtra_offset - state->data_offset;
861 state->action = cleanup_extracted;
865 * This should never happen.
867 else {
868 msg_warn("%s: message rejected: "
869 "unexpected record type %d in message content", myname, type);
870 state->errs |= CLEANUP_STAT_BAD;
874 /* cleanup_mime_error_callback - error report call-back routine */
876 static void cleanup_mime_error_callback(void *context, int err_code,
877 const char *text, ssize_t len)
879 CLEANUP_STATE *state = (CLEANUP_STATE *) context;
880 const char *origin;
883 * Message header too large errors are handled after the end of the
884 * primary message headers.
886 if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) {
887 if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
888 origin = MAIL_ATTR_ORG_NONE;
889 #define TEXT_LEN (len < 100 ? (int) len : 100)
890 msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>",
891 state->queue_id, mime_state_error(err_code), TEXT_LEN, text,
892 origin, state->sender, state->recip ? state->recip : "unknown");
896 /* cleanup_message - initialize message content segment */
898 void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len)
900 const char *myname = "cleanup_message";
901 int mime_options;
904 * Write the start-of-content segment marker.
906 cleanup_out_string(state, REC_TYPE_MESG, "");
907 if ((state->data_offset = vstream_ftell(state->dst)) < 0)
908 msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
911 * Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables
912 * special processing of Content-Type: headers, and thus, causes all text
913 * after the primary headers to be treated as the message body.
915 mime_options = 0;
916 if (var_disable_mime_input) {
917 mime_options |= MIME_OPT_DISABLE_MIME;
918 } else {
919 /* Turn off content checks if bouncing or forwarding mail. */
920 if (state->flags & CLEANUP_FLAG_FILTER) {
921 if (var_strict_8bitmime || var_strict_7bit_hdrs)
922 mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER;
923 if (var_strict_8bitmime || var_strict_8bit_body)
924 mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY;
925 if (var_strict_encoding)
926 mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN;
927 if (var_strict_8bitmime || var_strict_7bit_hdrs
928 || var_strict_8bit_body || var_strict_encoding
929 || *var_header_checks || *var_mimehdr_checks
930 || *var_nesthdr_checks)
931 mime_options |= MIME_OPT_REPORT_NESTING;
934 state->mime_state = mime_state_alloc(mime_options,
935 cleanup_header_callback,
936 cleanup_header_done_callback,
937 cleanup_body_callback,
938 (MIME_STATE_ANY_END) 0,
939 cleanup_mime_error_callback,
940 (void *) state);
943 * XXX Workaround: truncate a long message header so that we don't exceed
944 * the default Sendmail libmilter request size limit of 65535.
946 #define KLUDGE_HEADER_LIMIT 60000
947 if ((cleanup_milters || state->milters)
948 && var_header_limit > KLUDGE_HEADER_LIMIT)
949 var_header_limit = KLUDGE_HEADER_LIMIT;
952 * Pass control to the header processing routine.
954 state->action = cleanup_message_headerbody;
955 cleanup_message_headerbody(state, type, buf, len);