7 /* process message segment
9 /* #include "cleanup.h"
11 /* void cleanup_message(state, type, buf, len)
12 /* CLEANUP_STATE *state;
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
27 /* Queue file and message processing state. This state is updated
28 /* as records are processed and as errors happen.
34 /* Record content length.
38 /* The Secure Mailer license must be distributed with this software.
41 /* IBM T.J. Watson Research
43 /* Yorktown Heights, NY 10598, USA
54 #ifdef STRCASECMP_IN_STRINGS_H
58 /* Utility library. */
66 #include <stringops.h>
73 #include <cleanup_user.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>
82 #include <mail_proto.h>
83 #include <mime_state.h>
86 #include <conv_time.h>
88 /* Application-specific. */
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
);
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
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 */
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
,
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
154 tree
= tok822_parse_limit(vstring_str(header_buf
)
155 + strlen(hdr_opts
->name
) + 1,
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
))
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
))
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
))
174 cleanup_masquerade_tree(*tpp
, cleanup_masq_domains
);
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) {
186 cleanup_fold_header(state
, header_buf
);
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
,
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
211 tree
= tok822_parse_limit(vstring_str(header_buf
)
212 + strlen(hdr_opts
->name
) + 1,
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
))
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
))
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
))
231 cleanup_masquerade_tree(*tpp
, cleanup_masq_domains
);
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) {
243 cleanup_fold_header(state
, header_buf
);
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
)
257 if ((attr
= nvtable_find(state
->attr
, MAIL_ATTR_LOG_ORIGIN
)) == 0)
259 vstring_sprintf(state
->temp1
, "%s: %s: %s %.200s from %s;",
260 state
->queue_id
, action
, class, content
, attr
);
262 vstring_sprintf_append(state
->temp1
, " from=<%s>", state
->sender
);
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
);
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
;
292 while (*optional_text
&& ISSPACE(*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
;
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",
313 *state
->reason
= '4';
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
;
322 state
->errs
|= CLEANUP_STAT_CONT
;
323 state
->flags
&= ~CLEANUP_FLAG_FILTER_ALL
;
324 cleanup_act_log(state
, "reject", context
, buf
, state
->reason
);
327 if (STREQUAL(value
, "WARN", command_len
)) {
328 cleanup_act_log(state
, "warning", context
, buf
, optional_text
);
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
);
340 myfree(state
->filter
);
341 state
->filter
= mystrdup(optional_text
);
342 cleanup_act_log(state
, "filter", context
, buf
, optional_text
);
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
;
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
;
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
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
);
376 cleanup_act_log(state
, "delay", context
, buf
, optional_text
);
377 state
->defer_delay
= defer_delay
;
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
);
392 cleanup_act_log(state
, "prepend", context
, buf
, optional_text
);
393 cleanup_out_string(state
, REC_TYPE_NORM
, optional_text
);
397 if (STREQUAL(value
, "REPLACE", command_len
)) {
398 if (*optional_text
== 0) {
399 msg_warn("REPLACE action without text in %s map", map_class
);
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
);
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 -- "
416 optional_text
, map_class
);
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
;
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 */
434 if (STREQUAL(value
, "OK", command_len
)) /* compat */
437 msg_warn("unknown command in %s map: %s", map_class
, value
);
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
,
448 CLEANUP_STATE
*state
= (CLEANUP_STATE
*) context
;
449 const char *myname
= "cleanup_header_callback";
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
;
465 const char *map_class
;
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
);
487 if ((value
= maps_find(checks
, header
, 0)) != 0) {
490 if ((result
= cleanup_act(state
, CLEANUP_ACT_CTXT_HEADER
,
491 header
, value
, map_class
))
492 == CLEANUP_ACT_DROP
) {
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.
508 cleanup_out_header(state
, header_buf
);
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
))
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
,
533 * Copy attachment etc. header blocks without further inspection.
535 if (header_class
!= MIME_HDR_PRIMARY
) {
536 cleanup_out_header(state
, header_buf
);
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.
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? */
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)
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
;
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
);
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
,
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)
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
)) {
766 if ((value
= maps_find(cleanup_body_checks
, buf
, 0)) != 0) {
769 if ((result
= cleanup_act(state
, CLEANUP_ACT_CTXT_BODY
,
770 buf
, value
, VAR_BODY_CHECKS
))
771 == CLEANUP_ACT_DROP
) {
773 } else if (result
!= buf
) {
774 cleanup_out(state
, type
, result
, strlen(result
));
775 myfree((char *) result
);
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
;
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",
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
)))
830 buf
= vstring_str(state
->stripped_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.
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
;
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";
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.
916 if (var_disable_mime_input
) {
917 mime_options
|= MIME_OPT_DISABLE_MIME
;
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
,
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
);