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