1 /*        $NetBSD: cleanup_milter.c,v 1.6 2025/02/25 19:15:44 christos Exp $    */
2 
3 /*++
4 /* NAME
5 /*        cleanup_milter 3
6 /* SUMMARY
7 /*        external mail filter support
8 /* SYNOPSIS
9 /*        #include <cleanup.h>
10 /*
11 /*        void      cleanup_milter_header_checks_init(void)
12 /*
13 /*        void      cleanup_milter_receive(state, count)
14 /*        CLEANUP_STATE *state;
15 /*        int       count;
16 /*
17 /*        void      cleanup_milter_inspect(state, milters)
18 /*        CLEANUP_STATE *state;
19 /*        MILTERS   *milters;
20 /*
21 /*        cleanup_milter_emul_mail(state, milters, sender)
22 /*        CLEANUP_STATE *state;
23 /*        MILTERS   *milters;
24 /*        const char *sender;
25 /*
26 /*        cleanup_milter_emul_rcpt(state, milters, recipient)
27 /*        CLEANUP_STATE *state;
28 /*        MILTERS   *milters;
29 /*        const char *recipient;
30 /*
31 /*        cleanup_milter_emul_data(state, milters)
32 /*        CLEANUP_STATE *state;
33 /*        MILTERS   *milters;
34 /* DESCRIPTION
35 /*        This module implements support for Sendmail-style mail
36 /*        filter (milter) applications, including in-place queue file
37 /*        modification.
38 /*
39 /*        cleanup_milter_header_checks_init() does pre-jail
40 /*        initializations.
41 /*
42 /*        cleanup_milter_receive() receives mail filter definitions,
43 /*        typically from an smtpd(8) server process, and registers
44 /*        local call-back functions for macro expansion and for queue
45 /*        file modification.
46 /*
47 /*        cleanup_milter_inspect() sends the current message headers
48 /*        and body to the mail filters that were received with
49 /*        cleanup_milter_receive(), or that are specified with the
50 /*        cleanup_milters configuration parameter.
51 /*
52 /*        cleanup_milter_emul_mail() emulates connect, helo and mail
53 /*        events for mail that does not arrive via the smtpd(8) server.
54 /*        The emulation pretends that mail arrives from localhost/127.0.0.1
55 /*        via ESMTP. Milters can reject emulated connect, helo, mail
56 /*        or data events, but not emulated rcpt events as described
57 /*        next.
58 /*
59 /*        cleanup_milter_emul_rcpt() emulates an rcpt event for mail
60 /*        that does not arrive via the smtpd(8) server. This reports
61 /*        a server configuration error condition when the milter
62 /*        rejects an emulated rcpt event.
63 /*
64 /*        cleanup_milter_emul_data() emulates a data event for mail
65 /*        that does not arrive via the smtpd(8) server.  It's OK for
66 /*        milters to reject emulated data events.
67 /* SEE ALSO
68 /*        milter(3) generic mail filter interface
69 /* DIAGNOSTICS
70 /*        Fatal errors: memory allocation problem.
71 /*        Panic: interface violation.
72 /*        Warnings: I/O errors (state->errs is updated accordingly).
73 /* LICENSE
74 /* .ad
75 /* .fi
76 /*        The Secure Mailer license must be distributed with this software.
77 /* AUTHOR(S)
78 /*        Wietse Venema
79 /*        IBM T.J. Watson Research
80 /*        P.O. Box 704
81 /*        Yorktown Heights, NY 10598, USA
82 /*
83 /*        Wietse Venema
84 /*        Google, Inc.
85 /*        111 8th Avenue
86 /*        New York, NY 10011, USA
87 /*--*/
88 
89 /* System library. */
90 
91 #include <sys_defs.h>
92 #include <sys/socket.h>                           /* AF_INET */
93 #include <string.h>
94 #include <errno.h>
95 
96 #ifdef STRCASECMP_IN_STRINGS_H
97 #include <strings.h>
98 #endif
99 
100 /* Utility library. */
101 
102 #include <msg.h>
103 #include <vstream.h>
104 #include <vstring.h>
105 #include <stringops.h>
106 #include <inet_proto.h>
107 
108 /* Global library. */
109 
110 #include <off_cvt.h>
111 #include <dsn_mask.h>
112 #include <rec_type.h>
113 #include <cleanup_user.h>
114 #include <record.h>
115 #include <rec_attr_map.h>
116 #include <mail_proto.h>
117 #include <mail_params.h>
118 #include <lex_822.h>
119 #include <is_header.h>
120 #include <quote_821_local.h>
121 #include <dsn_util.h>
122 #include <xtext.h>
123 #include <info_log_addr_form.h>
124 #include <header_opts.h>
125 
126 /* Application-specific. */
127 
128 #include <cleanup.h>
129 
130  /*
131   * How Postfix 2.4 edits queue file information:
132   *
133   * Mail filter applications (Milters) can send modification requests after
134   * receiving the end of the message body.  Postfix implements these
135   * modifications in the cleanup server, so that it can edit the queue file
136   * in place. This avoids the temporary files that would be needed when
137   * modifications were implemented in the SMTP server (Postfix normally does
138   * not store the whole message in main memory). Once a Milter is done
139   * editing, the queue file can be used as input for the next Milter, and so
140   * on. Finally, the cleanup server changes file permissions, calls fsync(),
141   * and waits for successful completion.
142   *
143   * To implement in-place queue file edits, we need to introduce surprisingly
144   * little change to the existing Postfix queue file structure.  All we need
145   * is a way to mark a record as deleted, and to jump from one place in the
146   * queue file to another. We could implement deleted records with jumps, but
147   * marking is sometimes simpler.
148   *
149   * Postfix does not store queue files as plain text files. Instead all
150   * information is stored in records with an explicit type and length, for
151   * sender, recipient, arrival time, and so on.  Even the content that makes
152   * up the message header and body is stored as records with explicit types
153   * and lengths.  This organization makes it very easy to mark a record as
154   * deleted, and to introduce the pointer records that we will use to jump
155   * from one place in a queue file to another place.
156   *
157   * - Deleting a recipient is easiest - simply modify the record type into one
158   * that is skipped by the software that delivers mail. We won't try to reuse
159   * the deleted recipient for other purposes. When deleting a recipient, we
160   * may need to delete multiple recipient records that result from virtual
161   * alias expansion of the original recipient address.
162   *
163   * - Replacing a header record involves pointer records. A record is replaced
164   * by overwriting it with a forward pointer to space after the end of the
165   * queue file, putting the new record there, followed by a reverse pointer
166   * to the record that follows the replaced header. To simplify
167   * implementation we follow a short header record with a filler record so
168   * that we can always overwrite a header record with a pointer.
169   *
170   * N.B. This is a major difference with Postfix version 2.3, which needed
171   * complex code to save records that follow a short header, before it could
172   * overwrite a short header record. This code contained two of the three
173   * post-release bugs that were found with Postfix header editing.
174   *
175   * - Inserting a header record is like replacing one, except that we also
176   * relocate the record that is being overwritten by the forward pointer.
177   *
178   * - Deleting a message header is simplest when we replace it by a "skip"
179   * pointer to the information that follows the header. With a multi-line
180   * header we need to update only the first line.
181   *
182   * - Appending a recipient or header record involves pointer records as well.
183   * To make this convenient, the queue file already contains dummy pointer
184   * records at the locations where we want to append recipient or header
185   * content. To append, change the dummy pointer into a forward pointer to
186   * space after the end of a message, put the new recipient or header record
187   * there, followed by a reverse pointer to the record that follows the
188   * forward pointer.
189   *
190   * - To append another header or recipient record, replace the reverse pointer
191   * by a forward pointer to space after the end of a message, put the new
192   * record there, followed by the value of the reverse pointer that we
193   * replace. Thus, there is no one-to-one correspondence between forward and
194   * backward pointers. Instead, there can be multiple forward pointers for
195   * one reverse pointer.
196   *
197   * - When a mail filter wants to replace an entire body, we overwrite existing
198   * body records until we run out of space, and then write a pointer to space
199   * after the end of the queue file, followed by more body content. There may
200   * be multiple regions with body content; regions are connected by forward
201   * pointers, and the last region ends with a pointer to the marker that ends
202   * the message content segment. Body regions can be large and therefore they
203   * are reused to avoid wasting space. Sendmail mail filters currently do not
204   * replace individual body records, and that is a good thing.
205   *
206   * Making queue file modifications safe:
207   *
208   * Postfix queue files are segmented. The first segment is for envelope
209   * records, the second for message header and body content, and the third
210   * segment is for information that was extracted or generated from the
211   * message header or body content.  Each segment is terminated by a marker
212   * record. For now we don't want to change their location. That is, we want
213   * to avoid moving the records that mark the start or end of a queue file
214   * segment.
215   *
216   * To ensure that we can always replace a header or body record by a pointer
217   * record, without having to relocate a marker record, the cleanup server
218   * places a dummy pointer record at the end of the recipients and at the end
219   * of the message header. To support message body modifications, a dummy
220   * pointer record is also placed at the end of the message content.
221   *
222   * With all these changes in queue file organization, REC_TYPE_END is no longer
223   * guaranteed to be the last record in a queue file. If an application were
224   * to read beyond the REC_TYPE_END marker, it would go into an infinite
225   * loop, because records after REC_TYPE_END alternate with reverse pointers
226   * to the middle of the queue file. For robustness, the record reading
227   * routine skips forward to the end-of-file position after reading the
228   * REC_TYPE_END marker.
229   */
230 
231 /*#define msg_verbose         2*/
232 
233 static HBC_CHECKS *cleanup_milter_hbc_checks;
234 static VSTRING *cleanup_milter_hbc_reply;
235 static void cleanup_milter_set_error(CLEANUP_STATE *, int);
236 static const char *cleanup_add_rcpt_par(void *, const char *, const char *);
237 
238 #define STR(x)                vstring_str(x)
239 #define LEN(x)                VSTRING_LEN(x)
240 
241 /* cleanup_milter_hbc_log - log post-milter header/body_checks action */
242 
cleanup_milter_hbc_log(void * context,const char * action,const char * where,const char * line,const char * optional_text)243 static void cleanup_milter_hbc_log(void *context, const char *action,
244                                                 const char *where, const char *line,
245                                                    const char *optional_text)
246 {
247     const CLEANUP_STATE *state = (CLEANUP_STATE *) context;
248     const char *attr;
249 
250     vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.200s from %s[%s];",
251                         state->queue_id, where, action, where, line,
252                         state->client_name, state->client_addr);
253     if (state->sender)
254           vstring_sprintf_append(state->temp1, " from=<%s>",
255                                      info_log_addr_form_sender(state->sender));
256     if (state->recip)
257           vstring_sprintf_append(state->temp1, " to=<%s>",
258                                      info_log_addr_form_recipient(state->recip));
259     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
260           vstring_sprintf_append(state->temp1, " proto=%s", attr);
261     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
262           vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
263     if (optional_text)
264           vstring_sprintf_append(state->temp1, ": %s", optional_text);
265     msg_info("%s", vstring_str(state->temp1));
266 }
267 
268 /* cleanup_milter_header_prepend - prepend header to milter-generated header */
269 
cleanup_milter_header_prepend(void * context,int rec_type,const char * buf,ssize_t len,off_t offset)270 static void cleanup_milter_header_prepend(void *context, int rec_type,
271                                        const char *buf, ssize_t len, off_t offset)
272 {
273     /* XXX save prepended header to buffer. */
274     msg_warn("the milter_header/body_checks prepend action is not implemented");
275 }
276 
277 /* cleanup_milter_hbc_extend - additional header/body_checks actions */
278 
cleanup_milter_hbc_extend(void * context,const char * command,ssize_t cmd_len,const char * optional_text,const char * where,const char * buf,ssize_t buf_len,off_t offset)279 static char *cleanup_milter_hbc_extend(void *context, const char *command,
280                                        ssize_t cmd_len, const char *optional_text,
281                                                  const char *where, const char *buf,
282                                                        ssize_t buf_len, off_t offset)
283 {
284     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
285     const char *map_class = VAR_MILT_HEAD_CHECKS; /* XXX */
286 
287 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
288 
289     /*
290      * These are currently our mutually-exclusive ways of not receiving mail:
291      * "reject" and "discard". Only these can be reported to the up-stream
292      * Postfix libmilter code, because sending any reply there causes Postfix
293      * libmilter to skip further "edit" requests. By way of safety net, each
294      * of these must also reset CLEANUP_FLAG_FILTER_ALL.
295      */
296 #define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \
297     ((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT))
298 
299     /*
300      * We log all header/body-checks actions here, because we know the
301      * details of the message content that triggered the action. We report
302      * detail-free milter-reply values (reject/discard, stored in the
303      * milter_hbc_reply state member) to the Postfix libmilter code, so that
304      * Postfix libmilter can stop sending requests.
305      *
306      * We also set all applicable cleanup flags here, because there is no
307      * guarantee that Postfix libmilter will propagate our own milter-reply
308      * value to cleanup_milter_inspect() which calls cleanup_milter_apply().
309      * The latter translates responses from Milter applications into cleanup
310      * flags, and logs the response text. Postfix libmilter can convey only
311      * one milter-reply value per email message, and that reply may even come
312      * from outside Postfix.
313      *
314      * To suppress redundant logging, cleanup_milter_apply() does nothing when
315      * the milter-reply value matches the saved text in the milter_hbc_reply
316      * state member. As we remember only one milter-reply value, we can't
317      * report multiple milter-reply values per email message. We satisfy this
318      * constraint, because we already clear the CLEANUP_FLAG_FILTER_ALL flags
319      * to terminate further header inspection.
320      */
321     if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0)
322           return ((char *) buf);
323 
324     if (STREQUAL(command, "BCC", cmd_len)) {
325           if (strchr(optional_text, '@') == 0) {
326               msg_warn("bad BCC address \"%s\" in %s map -- "
327                          "need user@domain",
328                          optional_text, VAR_MILT_HEAD_CHECKS);
329           } else {
330               cleanup_milter_hbc_log(context, "bcc", where, buf, optional_text);
331               /* Caller checks state error flags. */
332               (void) cleanup_add_rcpt_par(state, optional_text, "");
333           }
334           return ((char *) buf);
335     }
336     if (STREQUAL(command, "REJECT", cmd_len)) {
337           const CLEANUP_STAT_DETAIL *detail;
338 
339           if (state->reason)
340               myfree(state->reason);
341           detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
342           if (*optional_text) {
343               state->reason = dsn_prepend(detail->dsn, optional_text);
344               if (*state->reason != '4' && *state->reason != '5') {
345                     msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
346                                optional_text);
347                     *state->reason = '4';
348               }
349           } else {
350               state->reason = dsn_prepend(detail->dsn, detail->text);
351           }
352           if (*state->reason == '4')
353               state->errs |= CLEANUP_STAT_DEFER;
354           else
355               state->errs |= CLEANUP_STAT_CONT;
356           state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
357           cleanup_milter_hbc_log(context, "reject", where, buf, state->reason);
358           vstring_sprintf(cleanup_milter_hbc_reply, "%d %s",
359                               detail->smtp, state->reason);
360           STR(cleanup_milter_hbc_reply)[0] = *state->reason;
361           return ((char *) buf);
362     }
363     if (STREQUAL(command, "FILTER", cmd_len)) {
364           if (*optional_text == 0) {
365               msg_warn("missing FILTER command argument in %s map", map_class);
366           } else if (strchr(optional_text, ':') == 0) {
367               msg_warn("bad FILTER command %s in %s -- "
368                          "need transport:destination",
369                          optional_text, map_class);
370           } else {
371               if (state->filter)
372                     myfree(state->filter);
373               state->filter = mystrdup(optional_text);
374               cleanup_milter_hbc_log(context, "filter", where, buf,
375                                            optional_text);
376           }
377           return ((char *) buf);
378     }
379     if (STREQUAL(command, "DISCARD", cmd_len)) {
380           cleanup_milter_hbc_log(context, "discard", where, buf, optional_text);
381           vstring_strcpy(cleanup_milter_hbc_reply, "D");
382           state->flags |= CLEANUP_FLAG_DISCARD;
383           state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
384           return ((char *) buf);
385     }
386     if (STREQUAL(command, "HOLD", cmd_len)) {
387           if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
388               cleanup_milter_hbc_log(context, "hold", where, buf, optional_text);
389               state->flags |= CLEANUP_FLAG_HOLD;
390           }
391           return ((char *) buf);
392     }
393     if (STREQUAL(command, "REDIRECT", cmd_len)) {
394           if (strchr(optional_text, '@') == 0) {
395               msg_warn("bad REDIRECT target \"%s\" in %s map -- "
396                          "need user@domain",
397                          optional_text, map_class);
398           } else {
399               if (state->redirect)
400                     myfree(state->redirect);
401               state->redirect = mystrdup(optional_text);
402               cleanup_milter_hbc_log(context, "redirect", where, buf,
403                                            optional_text);
404               state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
405           }
406           return ((char *) buf);
407     }
408     return ((char *) HBC_CHECKS_STAT_UNKNOWN);
409 }
410 
411 /* cleanup_milter_header_checks - inspect Milter-generated header */
412 
cleanup_milter_header_checks(CLEANUP_STATE * state,VSTRING * buf)413 static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf)
414 {
415     char   *ret;
416 
417     /*
418      * Milter application "add/insert/replace header" requests happen at the
419      * end-of-message stage, therefore all the header operations are relative
420      * to the primary message header.
421      */
422     ret = hbc_header_checks((void *) state, cleanup_milter_hbc_checks,
423                                   MIME_HDR_PRIMARY, (HEADER_OPTS *) 0,
424                                   buf, (off_t) 0);
425     if (ret == 0) {
426           return (0);
427     } else if (ret == HBC_CHECKS_STAT_ERROR) {
428           msg_warn("%s: %s map lookup problem -- "
429                      "message not accepted, try again later",
430                      state->queue_id, VAR_MILT_HEAD_CHECKS);
431           state->errs |= CLEANUP_STAT_WRITE;
432           return (0);
433     } else {
434           if (ret != STR(buf)) {
435               vstring_strcpy(buf, ret);
436               myfree(ret);
437           }
438           return (1);
439     }
440 }
441 
442 /* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */
443 
cleanup_milter_hbc_add_meta_records(CLEANUP_STATE * state)444 static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state)
445 {
446     const char *myname = "cleanup_milter_hbc_add_meta_records";
447     off_t   reverse_ptr_offset;
448     off_t   new_meta_offset;
449 
450     /*
451      * Note: this code runs while the Milter infrastructure is being torn
452      * down. For this reason we handle all I/O errors here on the spot,
453      * instead of reporting them back through the Milter infrastructure.
454      */
455 
456     /*
457      * Sanity check.
458      */
459     if (state->append_meta_pt_offset < 0)
460           msg_panic("%s: no meta append pointer location", myname);
461     if (state->append_meta_pt_target < 0)
462           msg_panic("%s: no meta append pointer target", myname);
463 
464     /*
465      * Allocate space after the end of the queue file, and write the meta
466      * record(s), followed by a reverse pointer record that points to the
467      * target of the old "meta record append" pointer record. This reverse
468      * pointer record becomes the new "meta record append" pointer record.
469      * Although the new "meta record append" pointer record will never be
470      * used, we update it here to make the code more similar to other code
471      * that inserts/appends content, so that common code can be factored out
472      * later.
473      */
474     if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
475           cleanup_milter_set_error(state, errno);
476           return;
477     }
478     if (state->filter != 0)
479           cleanup_out_string(state, REC_TYPE_FILT, state->filter);
480     if (state->redirect != 0)
481           cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
482     if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
483           msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
484           state->errs |= CLEANUP_STAT_WRITE;
485           return;
486     }
487     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
488                            (long) state->append_meta_pt_target);
489 
490     /*
491      * Pointer flipping: update the old "meta record append" pointer record
492      * value with the location of the new meta record.
493      */
494     if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) {
495           cleanup_milter_set_error(state, errno);
496           return;
497     }
498     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
499                            (long) new_meta_offset);
500 
501     /*
502      * Update the in-memory "meta append" pointer record location with the
503      * location of the reverse pointer record that follows the new meta
504      * record. The target of the "meta append" pointer record does not
505      * change; it's always the record that follows the dummy pointer record
506      * that was written while Postfix received the message.
507      */
508     state->append_meta_pt_offset = reverse_ptr_offset;
509 
510     /*
511      * Note: state->append_meta_pt_target never changes.
512      */
513 }
514 
515 /* cleanup_milter_header_checks_init - initialize post-Milter header checks */
516 
cleanup_milter_header_checks_init(void)517 void    cleanup_milter_header_checks_init(void)
518 {
519     static const char myname[] = "cleanup_milter_header_checks_init";
520 
521 #define NO_NESTED_HDR_NAME    ""
522 #define NO_NESTED_HDR_VALUE   ""
523 #define NO_MIME_HDR_NAME      ""
524 #define NO_MIME_HDR_VALUE     ""
525 
526     static /* XXX not const */ HBC_CALL_BACKS call_backs = {
527           cleanup_milter_hbc_log,
528           cleanup_milter_header_prepend,
529           cleanup_milter_hbc_extend,
530     };
531 
532     if (*var_milt_head_checks == 0)
533           msg_panic("%s: %s is empty", myname, VAR_MILT_HEAD_CHECKS);
534 
535     if (cleanup_milter_hbc_checks)
536           msg_panic("%s: cleanup_milter_hbc_checks is not null", myname);
537     cleanup_milter_hbc_checks =
538           hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks,
539                                          NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE,
540                                          NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE,
541                                          &call_backs);
542 
543     if (cleanup_milter_hbc_reply)
544           msg_panic("%s: cleanup_milter_hbc_reply is not null", myname);
545     cleanup_milter_hbc_reply = vstring_alloc(100);
546 }
547 
548 #ifdef TEST
549 
550 /* cleanup_milter_header_checks_deinit - undo cleanup_milter_header_checks_init */
551 
cleanup_milter_header_checks_deinit(void)552 static void cleanup_milter_header_checks_deinit(void)
553 {
554     static const char myname[] = "cleanup_milter_header_checks_deinit";
555 
556     if (cleanup_milter_hbc_checks == 0)
557           msg_panic("%s: cleanup_milter_hbc_checks is null", myname);
558     hbc_header_checks_free(cleanup_milter_hbc_checks);
559     cleanup_milter_hbc_checks = 0;
560 
561     if (cleanup_milter_hbc_reply == 0)
562           msg_panic("%s: cleanup_milter_hbc_reply is null", myname);
563     vstring_free(cleanup_milter_hbc_reply);
564     cleanup_milter_hbc_reply = 0;
565 }
566 
567 #endif
568 
569 /* cleanup_milter_header_checks_reinit - re-init post-Milter header checks */
570 
cleanup_milter_header_checks_reinit(CLEANUP_STATE * state)571 static void cleanup_milter_header_checks_reinit(CLEANUP_STATE *state)
572 {
573     if (state->filter)
574           myfree(state->filter);
575     state->filter = 0;
576     if (state->redirect)
577           myfree(state->redirect);
578     state->redirect = 0;
579     VSTRING_RESET(cleanup_milter_hbc_reply);
580 }
581 
582 /* cleanup_milter_hbc_finish - finalize post-Milter header checks */
583 
cleanup_milter_hbc_finish(CLEANUP_STATE * state)584 static void cleanup_milter_hbc_finish(CLEANUP_STATE *state)
585 {
586     if (CLEANUP_OUT_OK(state)
587           && !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)
588           && (state->filter || state->redirect))
589           cleanup_milter_hbc_add_meta_records(state);
590 }
591 
592  /*
593   * Milter replies.
594   */
595 #define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \
596           if ((__state)->reason) \
597               myfree((__state)->reason); \
598           (__state)->reason = mystrdup(__reason); \
599           if ((__state)->smtp_reply) { \
600               myfree((__state)->smtp_reply); \
601               (__state)->smtp_reply = 0; \
602           } \
603     } while (0)
604 
605 #define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \
606           if ((__state)->reason) \
607               myfree((__state)->reason); \
608           (__state)->reason = mystrdup(__smtp_reply + 4); \
609           printable((__state)->reason, '_'); \
610           if ((__state)->smtp_reply) \
611               myfree((__state)->smtp_reply); \
612           (__state)->smtp_reply = mystrdup(__smtp_reply); \
613     } while (0)
614 
615 /* cleanup_milter_set_error - set error flag from errno */
616 
cleanup_milter_set_error(CLEANUP_STATE * state,int err)617 static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
618 {
619     if (err == EFBIG) {
620           msg_warn("%s: queue file size limit exceeded", state->queue_id);
621           state->errs |= CLEANUP_STAT_SIZE;
622     } else {
623           msg_warn("%s: write queue file: %m", state->queue_id);
624           state->errs |= CLEANUP_STAT_WRITE;
625     }
626 }
627 
628 /* cleanup_milter_error - return dummy error description */
629 
cleanup_milter_error(CLEANUP_STATE * state,int err)630 static const char *cleanup_milter_error(CLEANUP_STATE *state, int err)
631 {
632     const char *myname = "cleanup_milter_error";
633     const CLEANUP_STAT_DETAIL *dp;
634 
635     /*
636      * For consistency with error reporting within the milter infrastructure,
637      * content manipulation routines return a null pointer on success, and an
638      * SMTP-like response on error.
639      *
640      * However, when cleanup_milter_apply() receives this error response from
641      * the milter infrastructure, it ignores the text since the appropriate
642      * cleanup error flags were already set by cleanup_milter_set_error().
643      *
644      * Specify a null error number when the "errno to error flag" mapping was
645      * already done elsewhere, possibly outside this module.
646      */
647     if (err)
648           cleanup_milter_set_error(state, err);
649     else if (CLEANUP_OUT_OK(state))
650           msg_panic("%s: missing errno to error flag mapping", myname);
651     if (state->milter_err_text == 0)
652           state->milter_err_text = vstring_alloc(50);
653     dp = cleanup_stat_detail(state->errs);
654     return (STR(vstring_sprintf(state->milter_err_text,
655                                         "%d %s %s", dp->smtp, dp->dsn, dp->text)));
656 }
657 
658 /* cleanup_add_header - append message header */
659 
cleanup_add_header(void * context,const char * name,const char * space,const char * value)660 static const char *cleanup_add_header(void *context, const char *name,
661                                                       const char *space,
662                                                       const char *value)
663 {
664     const char *myname = "cleanup_add_header";
665     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
666     VSTRING *buf;
667     off_t   reverse_ptr_offset;
668     off_t   new_hdr_offset;
669 
670     /*
671      * To simplify implementation, the cleanup server writes a dummy "header
672      * append" pointer record after the last message header. We cache both
673      * the location and the target of the current "header append" pointer
674      * record.
675      */
676     if (state->append_hdr_pt_offset < 0)
677           msg_panic("%s: no header append pointer location", myname);
678     if (state->append_hdr_pt_target < 0)
679           msg_panic("%s: no header append pointer target", myname);
680 
681     /*
682      * Return early when Milter header checks request that this header record
683      * be dropped, or that the message is discarded. Note: CLEANUP_OUT_OK()
684      * tests CLEANUP_FLAG_DISCARD. We don't want to report the latter as an
685      * error.
686      */
687     buf = vstring_alloc(100);
688     vstring_sprintf(buf, "%s:%s%s", name, space, value);
689     if (cleanup_milter_hbc_checks) {
690           if (cleanup_milter_header_checks(state, buf) == 0
691               || (state->flags & CLEANUP_FLAG_DISCARD)) {
692               vstring_free(buf);
693               return (0);
694           }
695           if (CLEANUP_OUT_OK(state) == 0) {
696               vstring_free(buf);
697               return (cleanup_milter_error(state, 0));
698           }
699     }
700 
701     /*
702      * Allocate space after the end of the queue file, and write the header
703      * record(s), followed by a reverse pointer record that points to the
704      * target of the old "header append" pointer record. This reverse pointer
705      * record becomes the new "header append" pointer record.
706      */
707     if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
708           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
709           vstring_free(buf);
710           return (cleanup_milter_error(state, errno));
711     }
712     /* XXX emit prepended header, then clear it. */
713     cleanup_out_header(state, buf);               /* Includes padding */
714     vstring_free(buf);
715     if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
716           msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
717           return (cleanup_milter_error(state, errno));
718     }
719     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
720                            (long) state->append_hdr_pt_target);
721 
722     /*
723      * Pointer flipping: update the old "header append" pointer record value
724      * with the location of the new header record.
725      *
726      * XXX To avoid unnecessary seek operations when the new header immediately
727      * follows the old append header pointer, write a null pointer or make
728      * the record reading loop smarter. Making vstream_fseek() smarter does
729      * not help, because it doesn't know if we're going to read or write
730      * after a write+seek sequence.
731      */
732     if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) {
733           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
734           return (cleanup_milter_error(state, errno));
735     }
736     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
737                            (long) new_hdr_offset);
738 
739     /*
740      * Update the in-memory "header append" pointer record location with the
741      * location of the reverse pointer record that follows the new header.
742      * The target of the "header append" pointer record does not change; it's
743      * always the record that follows the dummy pointer record that was
744      * written while Postfix received the message.
745      */
746     state->append_hdr_pt_offset = reverse_ptr_offset;
747 
748     /*
749      * In case of error while doing record output.
750      */
751     return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
752               cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
753               STR(cleanup_milter_hbc_reply) : 0);
754 
755     /*
756      * Note: state->append_hdr_pt_target never changes.
757      */
758 }
759 
760 /* hidden_header - respect milter header hiding protocol */
761 
hidden_header(VSTRING * buf,ARGV * auto_hdrs,int * hide_done)762 static int hidden_header(VSTRING *buf, ARGV *auto_hdrs, int *hide_done)
763 {
764     char  **cpp;
765     int     mask;
766 
767     for (cpp = auto_hdrs->argv, mask = 1; *cpp; cpp++, mask <<= 1)
768           if ((*hide_done & mask) == 0 && strncmp(*cpp, STR(buf), LEN(buf)) == 0)
769               return (*hide_done |= mask);
770     return (0);
771 }
772 
773 /* cleanup_find_header_start - find specific header instance */
774 
cleanup_find_header_start(CLEANUP_STATE * state,ssize_t index,const char * header_label,VSTRING * buf,int * prec_type,int allow_ptr_backup)775 static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index,
776                                                        const char *header_label,
777                                                        VSTRING *buf,
778                                                        int *prec_type,
779                                                        int allow_ptr_backup)
780 {
781     const char *myname = "cleanup_find_header_start";
782     off_t   curr_offset;                /* offset after found record */
783     off_t   ptr_offset;                           /* pointer to found record */
784     VSTRING *ptr_buf = 0;
785     int     rec_type = REC_TYPE_ERROR;
786     int     last_type;
787     ssize_t len;
788     int     hide_done = 0;
789 
790     if (msg_verbose)
791           msg_info("%s: index %ld name \"%s\"",
792                 myname, (long) index, header_label ? header_label : "(none)");
793 
794     /*
795      * Sanity checks.
796      */
797     if (index < 1)
798           msg_panic("%s: bad header index %ld", myname, (long) index);
799 
800     /*
801      * Skip to the start of the message content, and read records until we
802      * either find the specified header, or until we hit the end of the
803      * headers.
804      *
805      * The index specifies the header instance: 1 is the first one. The header
806      * label specifies the header name. A null pointer matches any header.
807      *
808      * When the specified header is not found, the result value is -1.
809      *
810      * When the specified header is found, its first record is stored in the
811      * caller-provided read buffer, and the result value is the queue file
812      * offset of that record. The file read position is left at the start of
813      * the next (non-filler) queue file record, which can be the remainder of
814      * a multi-record header.
815      *
816      * When a header is found and allow_ptr_backup is non-zero, then the result
817      * is either the first record of that header, or it is the pointer record
818      * that points to the first record of that header. In the latter case,
819      * the file read position is undefined. Returning the pointer allows us
820      * to do some optimizations when inserting text multiple times at the
821      * same place.
822      *
823      * XXX We can't use the MIME processor here. It not only buffers up the
824      * input, it also reads the record that follows a complete header before
825      * it invokes the header call-back action. This complicates the way that
826      * we discover header offsets and boundaries. Worse is that the MIME
827      * processor is unaware that multi-record message headers can have PTR
828      * records in the middle.
829      *
830      * XXX The draw-back of not using the MIME processor is that we have to
831      * duplicate some of its logic here and in the routine that finds the end
832      * of the header record. To minimize the duplication we define an ugly
833      * macro that is used in all code that scans for header boundaries.
834      *
835      * XXX Sendmail compatibility (based on Sendmail 8.13.6 measurements).
836      *
837      * - When changing Received: header #1, we change the Received: header that
838      * follows our own one; a request to change Received: header #0 is
839      * silently treated as a request to change Received: header #1.
840      *
841      * - When changing Date: header #1, we change the first Date: header; a
842      * request to change Date: header #0 is silently treated as a request to
843      * change Date: header #1.
844      *
845      * Thus, header change requests are relative to the content as received,
846      * that is, the content after our own Received: header. They can affect
847      * only the headers that the MTA actually exposes to mail filter
848      * applications.
849      *
850      * - However, when inserting a header at position 0, the new header appears
851      * before our own Received: header, and when inserting at position 1, the
852      * new header appears after our own Received: header.
853      *
854      * Thus, header insert operations are relative to the content as delivered,
855      * that is, the content including our own Received: header.
856      *
857      * None of the above is applicable after a Milter inserts a header before
858      * our own Received: header. From then on, our own Received: header
859      * becomes just like other headers.
860      */
861 #define CLEANUP_FIND_HEADER_NOTFOUND    (-1)
862 #define CLEANUP_FIND_HEADER_IOERROR     (-2)
863 
864 #define CLEANUP_FIND_HEADER_RETURN(offs) do { \
865           if (ptr_buf) \
866               vstring_free(ptr_buf); \
867           return (offs); \
868     } while (0)
869 
870 #define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \
871     if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \
872           msg_warn("%s: read file %s: %m", myname, cleanup_path); \
873           cleanup_milter_set_error(state, errno); \
874           do { quit; } while (0); \
875     } \
876     if (msg_verbose > 1) \
877           msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \
878                      LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \
879     if (rec_type == REC_TYPE_DTXT) \
880           continue; \
881     if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
882           && rec_type != REC_TYPE_PTR) \
883           break;
884     /* End of hairy macros. */
885 
886     if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0) {
887           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
888           cleanup_milter_set_error(state, errno);
889           CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
890     }
891     for (ptr_offset = 0, last_type = 0; /* void */ ; /* void */ ) {
892           if ((curr_offset = vstream_ftell(state->dst)) < 0) {
893               msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
894               cleanup_milter_set_error(state, errno);
895               CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
896           }
897           /* Don't follow the "append header" pointer. */
898           if (curr_offset == state->append_hdr_pt_offset)
899               break;
900           /* Caution: this macro terminates the loop at end-of-message. */
901           /* Don't do complex processing while breaking out of this loop. */
902           GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset,
903                        CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR));
904           /* Caution: don't assume ptr->header. This may be header-ptr->body. */
905           if (rec_type == REC_TYPE_PTR) {
906               if (rec_goto(state->dst, STR(buf)) < 0) {
907                     msg_warn("%s: read file %s: %m", myname, cleanup_path);
908                     cleanup_milter_set_error(state, errno);
909                     CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
910               }
911               /* Save PTR record, in case it points to the start of a header. */
912               if (allow_ptr_backup) {
913                     ptr_offset = curr_offset;
914                     if (ptr_buf == 0)
915                         ptr_buf = vstring_alloc(100);
916                     vstring_strcpy(ptr_buf, STR(buf));
917               }
918               /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */
919               continue;
920           }
921           /* The middle of a multi-record header. */
922           else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0])) {
923               /* Reset the saved PTR record and update last_type. */
924           }
925           /* No more message headers. */
926           else if ((len = is_header(STR(buf))) == 0) {
927               break;
928           }
929           /* This the start of a message header. */
930           else if ((header_label == 0
931                       || (strncasecmp(header_label, STR(buf), len) == 0
932                           && strlen(header_label) == len
933                           && !hidden_header(buf, state->auto_hdrs, &hide_done)))
934                      && --index == 0) {
935               /* If we have a saved PTR record, it points to start of header. */
936               break;
937           }
938           ptr_offset = 0;
939           last_type = rec_type;
940     }
941 
942     /*
943      * In case of failure, return negative start position.
944      */
945     if (index > 0) {
946           curr_offset = CLEANUP_FIND_HEADER_NOTFOUND;
947     } else {
948 
949           /*
950            * Skip over short-header padding, so that the file read pointer is
951            * always positioned at the first non-padding record after the header
952            * record. Insist on padding after short a header record, so that a
953            * short header record can safely be overwritten by a pointer record.
954            */
955           if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) {
956               VSTRING *rbuf = (ptr_offset ? buf :
957                                    (ptr_buf ? ptr_buf :
958                                     (ptr_buf = vstring_alloc(100))));
959               int     rval;
960 
961               if ((rval = rec_get_raw(state->dst, rbuf, 0, REC_FLAG_NONE)) < 0) {
962                     cleanup_milter_set_error(state, errno);
963                     CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
964               }
965               if (rval != REC_TYPE_DTXT)
966                     msg_panic("%s: short header without padding", myname);
967           }
968 
969           /*
970            * Optionally return a pointer to the message header, instead of the
971            * start of the message header itself. In that case the file read
972            * position is undefined (actually it is at the first non-padding
973            * record that follows the message header record).
974            */
975           if (ptr_offset != 0) {
976               rec_type = REC_TYPE_PTR;
977               curr_offset = ptr_offset;
978               vstring_strcpy(buf, STR(ptr_buf));
979           }
980           *prec_type = rec_type;
981     }
982     if (msg_verbose)
983           msg_info("%s: index %ld name %s type %d offset %ld",
984                      myname, (long) index, header_label ?
985                      header_label : "(none)", rec_type, (long) curr_offset);
986 
987     CLEANUP_FIND_HEADER_RETURN(curr_offset);
988 }
989 
990 /* cleanup_find_header_end - find end of header */
991 
cleanup_find_header_end(CLEANUP_STATE * state,VSTRING * rec_buf,int last_type)992 static off_t cleanup_find_header_end(CLEANUP_STATE *state,
993                                                      VSTRING *rec_buf,
994                                                      int last_type)
995 {
996     const char *myname = "cleanup_find_header_end";
997     off_t   read_offset;
998     int     rec_type;
999 
1000     /*
1001      * This routine is called immediately after cleanup_find_header_start().
1002      * rec_buf is the cleanup_find_header_start() result record; last_type is
1003      * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file
1004      * read position is at the first non-padding record after the result
1005      * header record.
1006      */
1007     for (;;) {
1008           if ((read_offset = vstream_ftell(state->dst)) < 0) {
1009               msg_warn("%s: read file %s: %m", myname, cleanup_path);
1010               cleanup_milter_error(state, errno);
1011               return (-1);
1012           }
1013           /* Don't follow the "append header" pointer. */
1014           if (read_offset == state->append_hdr_pt_offset)
1015               break;
1016           /* Caution: this macro terminates the loop at end-of-message. */
1017           /* Don't do complex processing while breaking out of this loop. */
1018           GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset,
1019           /* Warning and errno->error mapping are done elsewhere. */
1020                                             return (-1));
1021           if (rec_type == REC_TYPE_PTR) {
1022               if (rec_goto(state->dst, STR(rec_buf)) < 0) {
1023                     msg_warn("%s: read file %s: %m", myname, cleanup_path);
1024                     cleanup_milter_error(state, errno);
1025                     return (-1);
1026               }
1027               /* Don't update last_type; PTR may follow REC_TYPE_CONT. */
1028               continue;
1029           }
1030           /* Start of header or message body. */
1031           if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0]))
1032               break;
1033           last_type = rec_type;
1034     }
1035     return (read_offset);
1036 }
1037 
1038 /* cleanup_patch_header - patch new header into an existing header */
1039 
cleanup_patch_header(CLEANUP_STATE * state,const char * new_hdr_name,const char * hdr_space,const char * new_hdr_value,off_t old_rec_offset,int old_rec_type,VSTRING * old_rec_buf,off_t next_offset)1040 static const char *cleanup_patch_header(CLEANUP_STATE *state,
1041                                                           const char *new_hdr_name,
1042                                                           const char *hdr_space,
1043                                                           const char *new_hdr_value,
1044                                                           off_t old_rec_offset,
1045                                                           int old_rec_type,
1046                                                           VSTRING *old_rec_buf,
1047                                                           off_t next_offset)
1048 {
1049     const char *myname = "cleanup_patch_header";
1050     VSTRING *buf = vstring_alloc(100);
1051     off_t   new_hdr_offset;
1052 
1053 #define CLEANUP_PATCH_HEADER_RETURN(ret) do { \
1054           vstring_free(buf); \
1055           return (ret); \
1056     } while (0)
1057 
1058     if (msg_verbose)
1059           msg_info("%s: \"%s\" \"%s\" at %ld",
1060                      myname, new_hdr_name, new_hdr_value, (long) old_rec_offset);
1061 
1062     /*
1063      * Allocate space after the end of the queue file for the new header and
1064      * optionally save an existing record to make room for a forward pointer
1065      * record. If the saved record was not a PTR record, follow the saved
1066      * record by a reverse pointer record that points to the record after the
1067      * original location of the saved record.
1068      *
1069      * We update the queue file in a safe manner: save the new header and the
1070      * existing records after the end of the queue file, write the reverse
1071      * pointer, and only then overwrite the saved records with the forward
1072      * pointer to the new header.
1073      *
1074      * old_rec_offset, old_rec_type, and old_rec_buf specify the record that we
1075      * are about to overwrite with a pointer record. If the record needs to
1076      * be saved (i.e. old_rec_type > 0), the buffer contains the data content
1077      * of exactly one PTR or text record.
1078      *
1079      * next_offset specifies the record that follows the to-be-overwritten
1080      * record. It is ignored when the to-be-saved record is a pointer record.
1081      */
1082 
1083     /*
1084      * Return early when Milter header checks request that this header record
1085      * be dropped.
1086      */
1087     vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value);
1088     if (cleanup_milter_hbc_checks
1089           && cleanup_milter_header_checks(state, buf) == 0)
1090           CLEANUP_PATCH_HEADER_RETURN(0);
1091 
1092     /*
1093      * Write the new header to a new location after the end of the queue
1094      * file.
1095      */
1096     if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
1097           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1098           CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
1099     }
1100     /* XXX emit prepended header, then clear it. */
1101     cleanup_out_header(state, buf);               /* Includes padding */
1102     if (msg_verbose > 1)
1103           msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset,
1104                      LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf));
1105 
1106     /*
1107      * Optionally, save the existing text record or pointer record that will
1108      * be overwritten with the forward pointer. Pad a short saved record to
1109      * ensure that it, too, can be overwritten by a pointer.
1110      */
1111     if (old_rec_type > 0) {
1112           CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf);
1113           if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE)
1114               rec_pad(state->dst, REC_TYPE_DTXT,
1115                         REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf));
1116           if (msg_verbose > 1)
1117               msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ?
1118                          30 : (int) LEN(old_rec_buf), STR(old_rec_buf));
1119     }
1120 
1121     /*
1122      * If the saved record wasn't a PTR record, write the reverse pointer
1123      * after the saved records. A reverse pointer value of -1 means we were
1124      * confused about what we were going to save.
1125      */
1126     if (old_rec_type != REC_TYPE_PTR) {
1127           if (next_offset < 0)
1128               msg_panic("%s: bad reverse pointer %ld",
1129                           myname, (long) next_offset);
1130           cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1131                                  (long) next_offset);
1132           if (msg_verbose > 1)
1133               msg_info("%s: write PTR %ld", myname, (long) next_offset);
1134     }
1135 
1136     /*
1137      * Write the forward pointer over the old record. Generally, a pointer
1138      * record will be shorter than a header record, so there will be a gap in
1139      * the queue file before the next record. In other words, we must always
1140      * follow pointer records otherwise we get out of sync with the data.
1141      */
1142     if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0) {
1143           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1144           CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
1145     }
1146     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1147                            (long) new_hdr_offset);
1148     if (msg_verbose > 1)
1149           msg_info("%s: %ld: write PTR %ld", myname, (long) old_rec_offset,
1150                      (long) new_hdr_offset);
1151 
1152     /*
1153      * In case of error while doing record output.
1154      */
1155     CLEANUP_PATCH_HEADER_RETURN(
1156                  CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
1157                      cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
1158                                         STR(cleanup_milter_hbc_reply) : 0);
1159 
1160     /*
1161      * Note: state->append_hdr_pt_target never changes.
1162      */
1163 }
1164 
1165 /* cleanup_ins_header - insert message header */
1166 
cleanup_ins_header(void * context,ssize_t index,const char * new_hdr_name,const char * hdr_space,const char * new_hdr_value)1167 static const char *cleanup_ins_header(void *context, ssize_t index,
1168                                                       const char *new_hdr_name,
1169                                                       const char *hdr_space,
1170                                                       const char *new_hdr_value)
1171 {
1172     const char *myname = "cleanup_ins_header";
1173     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1174     VSTRING *old_rec_buf = vstring_alloc(100);
1175     off_t   old_rec_offset;
1176     int     old_rec_type;
1177     off_t   next_offset;
1178     const char *ret;
1179 
1180 #define CLEANUP_INS_HEADER_RETURN(ret) do { \
1181           vstring_free(old_rec_buf); \
1182           return (ret); \
1183     } while (0)
1184 
1185     if (msg_verbose)
1186           msg_info("%s: %ld \"%s\" \"%s\"",
1187                      myname, (long) index, new_hdr_name, new_hdr_value);
1188 
1189     /*
1190      * Look for a header at the specified position.
1191      *
1192      * The lookup result may be a pointer record. This allows us to make some
1193      * optimization when multiple insert operations happen in the same place.
1194      *
1195      * Index 1 is the top-most header.
1196      */
1197 #define NO_HEADER_NAME        ((char *) 0)
1198 #define ALLOW_PTR_BACKUP      1
1199 
1200     if (index < 1)
1201           index = 1;
1202     old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
1203                                                          old_rec_buf, &old_rec_type,
1204                                                          ALLOW_PTR_BACKUP);
1205     if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
1206           /* Warning and errno->error mapping are done elsewhere. */
1207           CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0));
1208 
1209     /*
1210      * If the header does not exist, simply append the header to the linked
1211      * list at the "header append" pointer record.
1212      */
1213     if (old_rec_offset < 0)
1214           CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
1215                                                              hdr_space, new_hdr_value));
1216 
1217     /*
1218      * If the header does exist, save both the new and the existing header to
1219      * new storage at the end of the queue file, and link the new storage
1220      * with a forward and reverse pointer (don't write a reverse pointer if
1221      * we are starting with a pointer record).
1222      */
1223     if (old_rec_type == REC_TYPE_PTR) {
1224           next_offset = -1;
1225     } else {
1226           if ((next_offset = vstream_ftell(state->dst)) < 0) {
1227               msg_warn("%s: read file %s: %m", myname, cleanup_path);
1228               CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno));
1229           }
1230     }
1231     ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
1232                                      old_rec_offset, old_rec_type,
1233                                      old_rec_buf, next_offset);
1234     CLEANUP_INS_HEADER_RETURN(ret);
1235 }
1236 
1237 /* cleanup_upd_header - modify or append message header */
1238 
cleanup_upd_header(void * context,ssize_t index,const char * new_hdr_name,const char * hdr_space,const char * new_hdr_value)1239 static const char *cleanup_upd_header(void *context, ssize_t index,
1240                                                       const char *new_hdr_name,
1241                                                       const char *hdr_space,
1242                                                       const char *new_hdr_value)
1243 {
1244     const char *myname = "cleanup_upd_header";
1245     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1246     VSTRING *rec_buf;
1247     off_t   old_rec_offset;
1248     off_t   next_offset;
1249     int     last_type;
1250     const char *ret;
1251 
1252     if (msg_verbose)
1253           msg_info("%s: %ld \"%s\" \"%s\"",
1254                      myname, (long) index, new_hdr_name, new_hdr_value);
1255 
1256     /*
1257      * Sanity check.
1258      */
1259     if (*new_hdr_name == 0)
1260           msg_panic("%s: null header name", myname);
1261 
1262     /*
1263      * Find the header that is being modified.
1264      *
1265      * The lookup result will never be a pointer record.
1266      *
1267      * Index 1 is the first matching header instance.
1268      *
1269      * XXX When a header is updated repeatedly we create jumps to jumps. To
1270      * eliminate this, rewrite the loop below so that we can start with the
1271      * pointer record that points to the header that's being edited.
1272      */
1273 #define DONT_SAVE_RECORD      0
1274 #define NO_PTR_BACKUP                   0
1275 
1276 #define CLEANUP_UPD_HEADER_RETURN(ret) do { \
1277           vstring_free(rec_buf); \
1278           return (ret); \
1279     } while (0)
1280 
1281     rec_buf = vstring_alloc(100);
1282     old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
1283                                                          rec_buf, &last_type,
1284                                                          NO_PTR_BACKUP);
1285     if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
1286           /* Warning and errno->error mapping are done elsewhere. */
1287           CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
1288 
1289     /*
1290      * If no old header is found, simply append the new header to the linked
1291      * list at the "header append" pointer record.
1292      */
1293     if (old_rec_offset < 0)
1294           CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
1295                                                              hdr_space, new_hdr_value));
1296 
1297     /*
1298      * If the old header is found, find the end of the old header, save the
1299      * new header to new storage at the end of the queue file, and link the
1300      * new storage with a forward and reverse pointer.
1301      */
1302     if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
1303           /* Warning and errno->error mapping are done elsewhere. */
1304           CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
1305     ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
1306                                      old_rec_offset, DONT_SAVE_RECORD,
1307                                      (VSTRING *) 0, next_offset);
1308     CLEANUP_UPD_HEADER_RETURN(ret);
1309 }
1310 
1311 /* cleanup_del_header - delete message header */
1312 
cleanup_del_header(void * context,ssize_t index,const char * hdr_name)1313 static const char *cleanup_del_header(void *context, ssize_t index,
1314                                                       const char *hdr_name)
1315 {
1316     const char *myname = "cleanup_del_header";
1317     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1318     VSTRING *rec_buf;
1319     off_t   header_offset;
1320     off_t   next_offset;
1321     int     last_type;
1322 
1323     if (msg_verbose)
1324           msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name);
1325 
1326     /*
1327      * Sanity check.
1328      */
1329     if (*hdr_name == 0)
1330           msg_panic("%s: null header name", myname);
1331 
1332     /*
1333      * Find the header that is being deleted.
1334      *
1335      * The lookup result will never be a pointer record.
1336      *
1337      * Index 1 is the first matching header instance.
1338      */
1339 #define CLEANUP_DEL_HEADER_RETURN(ret) do { \
1340           vstring_free(rec_buf); \
1341           return (ret); \
1342     } while (0)
1343 
1344     rec_buf = vstring_alloc(100);
1345     header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
1346                                                         &last_type, NO_PTR_BACKUP);
1347     if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
1348           /* Warning and errno->error mapping are done elsewhere. */
1349           CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
1350 
1351     /*
1352      * Overwrite the beginning of the header record with a pointer to the
1353      * information that follows the header. We can't simply overwrite the
1354      * header with cleanup_out_header() and a special record type, because
1355      * there may be a PTR record in the middle of a multi-line header.
1356      */
1357     if (header_offset > 0) {
1358           if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
1359               /* Warning and errno->error mapping are done elsewhere. */
1360               CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
1361           /* Mark the header as deleted. */
1362           if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0) {
1363               msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1364               CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno));
1365           }
1366           rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1367                         (long) next_offset);
1368     }
1369     vstring_free(rec_buf);
1370 
1371     /*
1372      * In case of error while doing record output.
1373      */
1374     return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
1375 }
1376 
1377 /* cleanup_chg_from - replace sender address, ignore ESMTP arguments */
1378 
cleanup_chg_from(void * context,const char * ext_from,const char * esmtp_args)1379 static const char *cleanup_chg_from(void *context, const char *ext_from,
1380                                                     const char *esmtp_args)
1381 {
1382     const char *myname = "cleanup_chg_from";
1383     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1384     off_t   new_offset;
1385     off_t   new_sender_offset;
1386     off_t   after_sender_offs;
1387     int     addr_count;
1388     TOK822 *tree;
1389     TOK822 *tp;
1390     VSTRING *int_sender_buf;
1391     int     dsn_envid = 0;
1392     int     dsn_ret = 0;
1393 
1394     if (msg_verbose)
1395           msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args);
1396 
1397     /*
1398      * ESMTP support is limited to RET and ENVID, i.e. things that are stored
1399      * together with the sender queue file record.
1400      */
1401     if (esmtp_args[0]) {
1402           ARGV   *esmtp_argv;
1403           int     i;
1404           const char *arg;
1405 
1406           esmtp_argv = argv_split(esmtp_args, " ");
1407           for (i = 0; i < esmtp_argv->argc; ++i) {
1408               arg = esmtp_argv->argv[i];
1409               if (strncasecmp(arg, "RET=", 4) == 0) {
1410                     if ((dsn_ret = dsn_ret_code(arg + 4)) == 0) {
1411                         msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
1412                                    "SMFI_CHGFROM request", arg);
1413                     } else {
1414                         state->dsn_ret = dsn_ret;
1415                     }
1416               } else if (strncasecmp(arg, "ENVID=", 6) == 0) {
1417                     if (state->milter_dsn_buf == 0)
1418                         state->milter_dsn_buf = vstring_alloc(20);
1419                     dsn_envid = (xtext_unquote(state->milter_dsn_buf, arg + 6)
1420                                    && allprint(STR(state->milter_dsn_buf)));
1421                     if (!dsn_envid) {
1422                         msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
1423                                    "SMFI_CHGFROM request", arg);
1424                     } else {
1425                         if (state->dsn_envid)
1426                               myfree(state->dsn_envid);
1427                         state->dsn_envid = mystrdup(STR(state->milter_dsn_buf));
1428                     }
1429               } else {
1430                     msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
1431                                "SMFI_CHGFROM request", arg);
1432               }
1433           }
1434           argv_free(esmtp_argv);
1435     }
1436 
1437     /*
1438      * The cleanup server remembers the file offset of the current sender
1439      * address record (offset in sender_pt_offset) and the file offset of the
1440      * record that follows the sender address (offset in sender_pt_target).
1441      * Short original sender records are padded, so that they can safely be
1442      * overwritten with a pointer record to the new sender address record.
1443      */
1444     if (state->sender_pt_offset < 0)
1445           msg_panic("%s: no original sender record offset", myname);
1446     if (state->sender_pt_target < 0)
1447           msg_panic("%s: no post-sender record offset", myname);
1448 
1449     /*
1450      * Allocate space after the end of the queue file, and write the new {DSN
1451      * envid, DSN ret, sender address, sender BCC} records, followed by a
1452      * reverse pointer record that points to the record that follows the
1453      * original sender record.
1454      *
1455      * We update the queue file in a safe manner: save the new sender after the
1456      * end of the queue file, write the reverse pointer, and only then
1457      * overwrite the old sender record with the forward pointer to the new
1458      * sender.
1459      */
1460     if ((new_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
1461           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1462           return (cleanup_milter_error(state, errno));
1463     }
1464 
1465     /*
1466      * Sender DSN attribute records precede the sender record.
1467      */
1468     if (dsn_envid)
1469           rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%s",
1470                         MAIL_ATTR_DSN_ENVID, STR(state->milter_dsn_buf));
1471     if (dsn_ret)
1472           rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%d",
1473                         MAIL_ATTR_DSN_RET, dsn_ret);
1474     if (dsn_envid == 0 && dsn_ret == 0) {
1475           new_sender_offset = new_offset;
1476     } else if ((new_sender_offset = vstream_ftell(state->dst)) < 0) {
1477           msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
1478           return (cleanup_milter_error(state, errno));
1479     }
1480 
1481     /*
1482      * Transform the address from external form to internal form. This also
1483      * removes the enclosing <>, if present.
1484      *
1485      * XXX vstring_alloc() rejects zero-length requests.
1486      */
1487     int_sender_buf = vstring_alloc(strlen(ext_from) + 1);
1488     tree = tok822_parse(ext_from);
1489     for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
1490           if (tp->type == TOK822_ADDR) {
1491               if (addr_count == 0) {
1492                     tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL);
1493                     addr_count += 1;
1494               } else {
1495                     msg_warn("%s: Milter request to add multi-sender: \"%s\"",
1496                                state->queue_id, ext_from);
1497                     break;
1498               }
1499           }
1500     }
1501     tok822_free_tree(tree);
1502     after_sender_offs = cleanup_addr_sender(state, STR(int_sender_buf));
1503     vstring_free(int_sender_buf);
1504     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1505                            (long) state->sender_pt_target);
1506     state->sender_pt_target = after_sender_offs;
1507 
1508     /*
1509      * Overwrite the current sender record with the pointer to the new {DSN
1510      * envid, DSN ret, sender address, sender BCC} records.
1511      */
1512     if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0) {
1513           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1514           return (cleanup_milter_error(state, errno));
1515     }
1516     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1517                            (long) new_offset);
1518 
1519     /*
1520      * Remember the location of the new current sender record.
1521      */
1522     state->sender_pt_offset = new_sender_offset;
1523 
1524     /*
1525      * In case of error while doing record output.
1526      */
1527     return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
1528 }
1529 
1530 /* cleanup_add_rcpt_par - append recipient address, with ESMTP arguments */
1531 
cleanup_add_rcpt_par(void * context,const char * ext_rcpt,const char * esmtp_args)1532 static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt,
1533                                                           const char *esmtp_args)
1534 {
1535     const char *myname = "cleanup_add_rcpt_par";
1536     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1537     off_t   new_rcpt_offset;
1538     off_t   reverse_ptr_offset;
1539     int     addr_count;
1540     TOK822 *tree;
1541     TOK822 *tp;
1542     VSTRING *int_rcpt_buf;
1543     VSTRING *orcpt_buf = 0;
1544     ARGV   *esmtp_argv;
1545     int     dsn_notify = 0;
1546     const char *dsn_orcpt_info = 0;
1547     size_t  type_len;
1548     int     i;
1549     const char *arg;
1550     const char *arg_val;
1551 
1552     if (msg_verbose)
1553           msg_info("%s: \"%s\" \"%s\"", myname, ext_rcpt, esmtp_args);
1554 
1555     /*
1556      * To simplify implementation, the cleanup server writes a dummy
1557      * "recipient append" pointer record after the last recipient. We cache
1558      * both the location and the target of the current "recipient append"
1559      * pointer record.
1560      */
1561     if (state->append_rcpt_pt_offset < 0)
1562           msg_panic("%s: no recipient append pointer location", myname);
1563     if (state->append_rcpt_pt_target < 0)
1564           msg_panic("%s: no recipient append pointer target", myname);
1565 
1566     /*
1567      * Allocate space after the end of the queue file, and write the
1568      * recipient record, followed by a reverse pointer record that points to
1569      * the target of the old "recipient append" pointer record. This reverse
1570      * pointer record becomes the new "recipient append" pointer record.
1571      *
1572      * We update the queue file in a safe manner: save the new recipient after
1573      * the end of the queue file, write the reverse pointer, and only then
1574      * overwrite the old "recipient append" pointer with the forward pointer
1575      * to the new recipient.
1576      */
1577     if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
1578           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1579           return (cleanup_milter_error(state, errno));
1580     }
1581 
1582     /*
1583      * Parse ESMTP parameters. XXX UTF8SMTP don't assume ORCPT is xtext.
1584      */
1585     if (esmtp_args[0]) {
1586           esmtp_argv = argv_split(esmtp_args, " ");
1587           for (i = 0; i < esmtp_argv->argc; ++i) {
1588               arg = esmtp_argv->argv[i];
1589               if (strncasecmp(arg, "NOTIFY=", 7) == 0) {    /* RFC 3461 */
1590                     if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0)
1591                         msg_warn("%s: Bad NOTIFY parameter from Milter or "
1592                                    "header/body_checks: \"%.100s\"",
1593                                    state->queue_id, arg);
1594               } else if (strncasecmp(arg, "ORCPT=", 6) == 0) {        /* RFC 3461 */
1595                     if (orcpt_buf == 0)
1596                         orcpt_buf = vstring_alloc(100);
1597                     if (dsn_orcpt_info
1598                         || (type_len = strcspn(arg_val = arg + 6, ";")) == 0
1599                         || (arg_val)[type_len] != ';'
1600                         || xtext_unquote_append(vstring_sprintf(orcpt_buf,
1601                                                                 "%.*s;", (int) type_len,
1602                                                                           arg_val),
1603                                                       arg_val + type_len + 1) == 0) {
1604                         msg_warn("%s: Bad ORCPT parameter from Milter or "
1605                                    "header/body_checks: \"%.100s\"",
1606                                    state->queue_id, arg);
1607                     } else {
1608                         dsn_orcpt_info = STR(orcpt_buf);
1609                     }
1610               } else {
1611                     msg_warn("%s: ignoring ESMTP argument from Milter or "
1612                                "header/body_checks: \"%.100s\"",
1613                                state->queue_id, arg);
1614               }
1615           }
1616           argv_free(esmtp_argv);
1617     }
1618 
1619     /*
1620      * Transform recipient from external form to internal form. This also
1621      * removes the enclosing <>, if present.
1622      *
1623      * XXX vstring_alloc() rejects zero-length requests.
1624      */
1625     int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
1626     tree = tok822_parse(ext_rcpt);
1627     for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
1628           if (tp->type == TOK822_ADDR) {
1629               if (addr_count == 0) {
1630                     tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
1631                     addr_count += 1;
1632               } else {
1633                     msg_warn("%s: Milter or header/body_checks request to "
1634                                "add multi-recipient: \"%s\"",
1635                                state->queue_id, ext_rcpt);
1636                     break;
1637               }
1638           }
1639     }
1640     tok822_free_tree(tree);
1641     if (addr_count != 0)
1642           cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), dsn_orcpt_info,
1643                                    dsn_notify ? dsn_notify : DEF_DSN_NOTIFY);
1644     else
1645           msg_warn("%s: ignoring attempt from Milter to add null recipient",
1646                      state->queue_id);
1647     vstring_free(int_rcpt_buf);
1648     if (orcpt_buf)
1649           vstring_free(orcpt_buf);
1650 
1651     /*
1652      * Don't update the queue file when we did not write a recipient record
1653      * (malformed or duplicate BCC recipient).
1654      */
1655     if (vstream_ftell(state->dst) == new_rcpt_offset)
1656           return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
1657 
1658     /*
1659      * Follow the recipient with a "reverse" pointer to the old recipient
1660      * append target.
1661      */
1662     if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
1663           msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
1664           return (cleanup_milter_error(state, errno));
1665     }
1666     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1667                            (long) state->append_rcpt_pt_target);
1668 
1669     /*
1670      * Pointer flipping: update the old "recipient append" pointer record
1671      * value to the location of the new recipient record.
1672      */
1673     if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0) {
1674           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1675           return (cleanup_milter_error(state, errno));
1676     }
1677     cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
1678                            (long) new_rcpt_offset);
1679 
1680     /*
1681      * Update the in-memory "recipient append" pointer record location with
1682      * the location of the reverse pointer record that follows the new
1683      * recipient. The target of the "recipient append" pointer record does
1684      * not change; it's always the record that follows the dummy pointer
1685      * record that was written while Postfix received the message.
1686      */
1687     state->append_rcpt_pt_offset = reverse_ptr_offset;
1688 
1689     /*
1690      * In case of error while doing record output.
1691      */
1692     return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
1693 }
1694 
1695 /* cleanup_add_rcpt - append recipient address */
1696 
cleanup_add_rcpt(void * context,const char * ext_rcpt)1697 static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt)
1698 {
1699     return (cleanup_add_rcpt_par(context, ext_rcpt, ""));
1700 }
1701 
1702 /* cleanup_del_rcpt - remove recipient and all its expansions */
1703 
cleanup_del_rcpt(void * context,const char * ext_rcpt)1704 static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt)
1705 {
1706     const char *myname = "cleanup_del_rcpt";
1707     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1708     off_t   curr_offset;
1709     VSTRING *buf;
1710     char   *attr_name;
1711     char   *attr_value;
1712     char   *dsn_orcpt = 0;              /* XXX for dup filter cleanup */
1713     int     dsn_notify = 0;             /* XXX for dup filter cleanup */
1714     char   *orig_rcpt = 0;
1715     char   *start;
1716     int     rec_type;
1717     int     junk;
1718     int     count = 0;
1719     TOK822 *tree;
1720     TOK822 *tp;
1721     VSTRING *int_rcpt_buf;
1722     int     addr_count;
1723 
1724     if (msg_verbose)
1725           msg_info("%s: \"%s\"", myname, ext_rcpt);
1726 
1727     /*
1728      * Virtual aliasing and other address rewriting happens after the mail
1729      * filter sees the envelope address. Therefore we must delete all
1730      * recipient records whose Postfix (not DSN) original recipient address
1731      * matches the specified address.
1732      *
1733      * As the number of recipients may be very large we can't do an efficient
1734      * two-pass implementation (collect record offsets first, then mark
1735      * records as deleted). Instead we mark records as soon as we find them.
1736      * This is less efficient because we do (seek-write-read) for each marked
1737      * recipient, instead of (seek-write). It's unlikely that VSTREAMs will
1738      * be made smart enough to eliminate unnecessary I/O with small seeks.
1739      *
1740      * XXX When Postfix original recipients are turned off, we have no option
1741      * but to match against the expanded and rewritten recipient address.
1742      *
1743      * XXX Remove the (dsn_orcpt, dsn_notify, orcpt, recip) tuple from the
1744      * duplicate recipient filter. This requires that we maintain reference
1745      * counts.
1746      */
1747     if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) {
1748           msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1749           return (cleanup_milter_error(state, errno));
1750     }
1751 #define CLEANUP_DEL_RCPT_RETURN(ret) do { \
1752           if (orig_rcpt != 0) \
1753               myfree(orig_rcpt); \
1754           if (dsn_orcpt != 0) \
1755               myfree(dsn_orcpt); \
1756           vstring_free(buf); \
1757           vstring_free(int_rcpt_buf); \
1758           return (ret); \
1759     } while (0)
1760 
1761     /*
1762      * Transform recipient from external form to internal form. This also
1763      * removes the enclosing <>, if present.
1764      *
1765      * XXX vstring_alloc() rejects zero-length requests.
1766      */
1767     int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
1768     tree = tok822_parse(ext_rcpt);
1769     for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
1770           if (tp->type == TOK822_ADDR) {
1771               if (addr_count == 0) {
1772                     tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
1773                     addr_count += 1;
1774               } else {
1775                     msg_warn("%s: Milter request to drop multi-recipient: \"%s\"",
1776                                state->queue_id, ext_rcpt);
1777                     break;
1778               }
1779           }
1780     }
1781     tok822_free_tree(tree);
1782 
1783     buf = vstring_alloc(100);
1784     for (;;) {
1785           if (CLEANUP_OUT_OK(state) == 0)
1786               /* Warning and errno->error mapping are done elsewhere. */
1787               CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0));
1788           if ((curr_offset = vstream_ftell(state->dst)) < 0) {
1789               msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
1790               CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1791           }
1792           if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) <= 0) {
1793               msg_warn("%s: read file %s: %m", myname, cleanup_path);
1794               CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1795           }
1796           if (rec_type == REC_TYPE_END)
1797               break;
1798           /* Skip over message content. */
1799           if (rec_type == REC_TYPE_MESG) {
1800               if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) {
1801                     msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1802                     CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1803               }
1804               continue;
1805           }
1806           start = STR(buf);
1807           if (rec_type == REC_TYPE_PTR) {
1808               if (rec_goto(state->dst, start) < 0) {
1809                     msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1810                     CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1811               }
1812               continue;
1813           }
1814           /* Map attribute names to pseudo record type. */
1815           if (rec_type == REC_TYPE_ATTR) {
1816               if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
1817                     || *attr_value == 0)
1818                     continue;
1819               if ((junk = rec_attr_map(attr_name)) != 0) {
1820                     start = attr_value;
1821                     rec_type = junk;
1822               }
1823           }
1824           switch (rec_type) {
1825           case REC_TYPE_DSN_ORCPT:                /* RCPT TO ORCPT parameter */
1826               if (dsn_orcpt != 0)                           /* can't happen */
1827                     myfree(dsn_orcpt);
1828               dsn_orcpt = mystrdup(start);
1829               break;
1830           case REC_TYPE_DSN_NOTIFY:               /* RCPT TO NOTIFY parameter */
1831               if (alldig(start) && (junk = atoi(start)) > 0
1832                     && DSN_NOTIFY_OK(junk))
1833                     dsn_notify = junk;
1834               else
1835                     dsn_notify = 0;
1836               break;
1837           case REC_TYPE_ORCP:                     /* unmodified RCPT TO address */
1838               if (orig_rcpt != 0)                           /* can't happen */
1839                     myfree(orig_rcpt);
1840               orig_rcpt = mystrdup(start);
1841               break;
1842           case REC_TYPE_RCPT:                     /* rewritten RCPT TO address */
1843               if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) {
1844                     if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) {
1845                         msg_warn("%s: seek file %s: %m", myname, cleanup_path);
1846                         CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1847                     }
1848                     if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0) {
1849                         msg_warn("%s: write queue file: %m", state->queue_id);
1850                         CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
1851                     }
1852                     count++;
1853               }
1854               if (var_enable_orcpt)
1855                     /* Matches been_here() call in cleanup_out_recipient(). */
1856                     been_here_drop(state->dups, "%s\n%d\n%s\n%s",
1857                                      dsn_orcpt ? dsn_orcpt : "", dsn_notify,
1858                                    orig_rcpt ? orig_rcpt : "", STR(int_rcpt_buf));
1859               /* FALLTHROUGH */
1860           case REC_TYPE_DRCP:                     /* canceled recipient */
1861           case REC_TYPE_DONE:                     /* can't happen */
1862               if (orig_rcpt != 0) {
1863                     myfree(orig_rcpt);
1864                     orig_rcpt = 0;
1865               }
1866               if (dsn_orcpt != 0) {
1867                     myfree(dsn_orcpt);
1868                     dsn_orcpt = 0;
1869               }
1870               dsn_notify = 0;
1871               break;
1872           }
1873     }
1874     /* Matches been_here_fixed() call in cleanup_out_recipient(). */
1875     if (var_enable_orcpt == 0 && count > 0)
1876           been_here_drop_fixed(state->dups, STR(int_rcpt_buf));
1877 
1878     if (msg_verbose)
1879           msg_info("%s: deleted %d records for recipient \"%s\"",
1880                      myname, count, ext_rcpt);
1881 
1882     CLEANUP_DEL_RCPT_RETURN(0);
1883 }
1884 
1885 /* cleanup_repl_body - replace message body */
1886 
cleanup_repl_body(void * context,int cmd,int rec_type,VSTRING * buf)1887 static const char *cleanup_repl_body(void *context, int cmd, int rec_type,
1888                                                      VSTRING *buf)
1889 {
1890     const char *myname = "cleanup_repl_body";
1891     CLEANUP_STATE *state = (CLEANUP_STATE *) context;
1892     static VSTRING empty;
1893 
1894     /*
1895      * XXX Sendmail compatibility: milters don't see the first body line, so
1896      * don't expect they will send one.
1897      */
1898     switch (cmd) {
1899     case MILTER_BODY_LINE:
1900           if (cleanup_body_edit_write(state, rec_type, buf) < 0)
1901               return (cleanup_milter_error(state, errno));
1902           break;
1903     case MILTER_BODY_START:
1904           VSTRING_RESET(&empty);
1905           if (cleanup_body_edit_start(state) < 0
1906               || cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0)
1907               return (cleanup_milter_error(state, errno));
1908           break;
1909     case MILTER_BODY_END:
1910           if (cleanup_body_edit_finish(state) < 0)
1911               return (cleanup_milter_error(state, errno));
1912           break;
1913     default:
1914           msg_panic("%s: bad command: %d", myname, cmd);
1915     }
1916     return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno));
1917 }
1918 
1919 /* cleanup_milter_eval - expand macro */
1920 
cleanup_milter_eval(const char * name,void * ptr)1921 static const char *cleanup_milter_eval(const char *name, void *ptr)
1922 {
1923     CLEANUP_STATE *state = (CLEANUP_STATE *) ptr;
1924 
1925     /*
1926      * Note: if we use XFORWARD attributes here, then consistency requires
1927      * that we forward all Sendmail macros via XFORWARD.
1928      */
1929 
1930     /*
1931      * System macros.
1932      */
1933     if (strcmp(name, S8_MAC_DAEMON_NAME) == 0)
1934           return (var_milt_daemon_name);
1935     if (strcmp(name, S8_MAC_V) == 0)
1936           return (var_milt_v);
1937 
1938     /*
1939      * Connect macros.
1940      */
1941 #ifndef CLIENT_ATTR_UNKNOWN
1942 #define CLIENT_ATTR_UNKNOWN "unknown"
1943 #define SERVER_ATTR_UNKNOWN "unknown"
1944 #endif
1945 
1946     if (strcmp(name, S8_MAC__) == 0) {
1947           vstring_sprintf(state->temp1, "%s [%s]",
1948                               state->reverse_name, state->client_addr);
1949           if (strcasecmp(state->client_name, state->reverse_name) != 0)
1950               vstring_strcat(state->temp1, " (may be forged)");
1951           return (STR(state->temp1));
1952     }
1953     if (strcmp(name, S8_MAC_J) == 0)
1954           return (var_myhostname);
1955     if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0)
1956           return (state->client_addr);
1957     if (strcmp(name, S8_MAC_CLIENT_NAME) == 0)
1958           return (state->client_name);
1959     if (strcmp(name, S8_MAC_CLIENT_PORT) == 0)
1960           return (state->client_port
1961                     && strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ?
1962                     state->client_port : "0");
1963     if (strcmp(name, S8_MAC_CLIENT_PTR) == 0)
1964           return (state->reverse_name);
1965     /* XXX S8_MAC_CLIENT_RES needs SMTPD_PEER_CODE_XXX from smtpd. */
1966     if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0)
1967           return (state->server_addr);
1968     if (strcmp(name, S8_MAC_DAEMON_PORT) == 0)
1969           return (state->server_port
1970                     && strcmp(state->server_port, SERVER_ATTR_UNKNOWN) ?
1971                     state->server_port : "0");
1972 
1973     /*
1974      * MAIL FROM macros.
1975      */
1976     if (strcmp(name, S8_MAC_I) == 0)
1977           return (state->queue_id);
1978 #ifdef USE_SASL_AUTH
1979     if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
1980           return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD));
1981     if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
1982           return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME));
1983     if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
1984           return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER));
1985 #endif
1986     if (strcmp(name, S8_MAC_MAIL_ADDR) == 0)
1987           return (state->milter_ext_from ? STR(state->milter_ext_from) : 0);
1988 
1989     /*
1990      * RCPT TO macros.
1991      */
1992     if (strcmp(name, S8_MAC_RCPT_ADDR) == 0)
1993           return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0);
1994     return (0);
1995 }
1996 
1997 /* cleanup_milter_receive - receive milter instances */
1998 
cleanup_milter_receive(CLEANUP_STATE * state,int count)1999 void    cleanup_milter_receive(CLEANUP_STATE *state, int count)
2000 {
2001     if (state->milters)
2002           milter_free(state->milters);
2003     state->milters = milter_receive(state->src, count);
2004     if (state->milters == 0)
2005           msg_fatal("cleanup_milter_receive: milter receive failed");
2006     if (count <= 0)
2007           return;
2008     milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state);
2009     milter_edit_callback(state->milters,
2010                                cleanup_add_header, cleanup_upd_header,
2011                                cleanup_ins_header, cleanup_del_header,
2012                                cleanup_chg_from, cleanup_add_rcpt,
2013                                cleanup_add_rcpt_par, cleanup_del_rcpt,
2014                                cleanup_repl_body, (void *) state);
2015 }
2016 
2017 /* cleanup_milter_apply - apply Milter response, non-zero if rejecting */
2018 
cleanup_milter_apply(CLEANUP_STATE * state,const char * event,const char * resp)2019 static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event,
2020                                                           const char *resp)
2021 {
2022     const char *myname = "cleanup_milter_apply";
2023     const char *action;
2024     const char *text;
2025     const char *attr;
2026     const char *ret = 0;
2027 
2028     if (msg_verbose)
2029           msg_info("%s: %s", myname, resp);
2030 
2031     /*
2032      * Don't process our own milter_header/body checks replies. See comments
2033      * in cleanup_milter_hbc_extend().
2034      */
2035     if (cleanup_milter_hbc_reply &&
2036           strcmp(resp, STR(cleanup_milter_hbc_reply)) == 0)
2037           return (0);
2038 
2039     /*
2040      * Don't process Milter replies that are redundant because header/body
2041      * checks already decided that we will not receive the message; or Milter
2042      * replies that would have conflicting effect with the outcome of
2043      * header/body checks (for example, header_checks "discard" action
2044      * followed by Milter "reject" reply). Logging both actions would look
2045      * silly.
2046      */
2047     if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) {
2048           if (msg_verbose)
2049               msg_info("%s: ignoring redundant or conflicting milter reply: %s",
2050                          state->queue_id, resp);
2051           return (0);
2052     }
2053 
2054     /*
2055      * Sanity check.
2056      */
2057     if (state->client_name == 0)
2058           msg_panic("%s: missing client info initialization", myname);
2059 
2060     /*
2061      * We don't report errors that were already reported by the content
2062      * editing call-back routines. See cleanup_milter_error() above.
2063      */
2064     if (CLEANUP_OUT_OK(state) == 0)
2065           return (0);
2066     switch (resp[0]) {
2067     case 'H':
2068           if (state->flags & CLEANUP_FLAG_HOLD)
2069               return (0);
2070           state->flags |= CLEANUP_FLAG_HOLD;
2071           action = "milter-hold";
2072           text = resp[1] ? resp + 1 : "milter triggers HOLD action";
2073           break;
2074     case 'D':
2075           if (state->flags & CLEANUP_FLAG_DISCARD)
2076               return (0);
2077           state->flags |= CLEANUP_FLAG_DISCARD;
2078           action = "milter-discard";
2079           text = "milter triggers DISCARD action";
2080           break;
2081     case 'S':
2082           if (state->flags & CLEANUP_STAT_CONT)
2083               return (0);
2084           /* XXX Can this happen after end-of-message? */
2085           state->flags |= CLEANUP_STAT_CONT;
2086           action = "milter-reject";
2087           text = cleanup_strerror(CLEANUP_STAT_CONT);
2088           break;
2089 
2090           /*
2091            * Override permanent reject with temporary reject. This happens when
2092            * the cleanup server has to bounce (hard reject) but is unable to
2093            * store the message (soft reject). After a temporary reject we stop
2094            * inspecting queue file records, so it can't be overruled by
2095            * something else.
2096            *
2097            * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
2098            * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
2099            * queue record processing, and prevents bounces from being sent.
2100            */
2101     case '4':
2102           CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
2103           ret = state->reason;
2104           state->errs |= CLEANUP_STAT_DEFER;
2105           action = "milter-reject";
2106           text = resp + 4;
2107           break;
2108     case '5':
2109           CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
2110           ret = state->reason;
2111           state->errs |= CLEANUP_STAT_CONT;
2112           action = "milter-reject";
2113           text = resp + 4;
2114           break;
2115     default:
2116           msg_panic("%s: unexpected mail filter reply: %s", myname, resp);
2117     }
2118     vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;",
2119                         state->queue_id, action, event, state->client_name,
2120                         state->client_addr, text);
2121     if (state->sender)
2122           vstring_sprintf_append(state->temp1, " from=<%s>",
2123                                      info_log_addr_form_sender(state->sender));
2124     if (state->recip)
2125           vstring_sprintf_append(state->temp1, " to=<%s>",
2126                                      info_log_addr_form_recipient(state->recip));
2127     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
2128           vstring_sprintf_append(state->temp1, " proto=%s", attr);
2129     if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
2130           vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
2131     msg_info("%s", vstring_str(state->temp1));
2132 
2133     return (ret);
2134 }
2135 
2136 /* cleanup_milter_client_init - initialize real or ersatz client info */
2137 
cleanup_milter_client_init(CLEANUP_STATE * state)2138 static void cleanup_milter_client_init(CLEANUP_STATE *state)
2139 {
2140     static const INET_PROTO_INFO *proto_info;
2141     const char *proto_attr;
2142 
2143     /*
2144      * Either the cleanup client specifies a name, address and protocol, or
2145      * we have a local submission and pretend localhost/127.0.0.1/AF_INET.
2146      */
2147 #define NO_CLIENT_PORT        "0"
2148 
2149     state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME);
2150     state->reverse_name =
2151           nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME);
2152     state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR);
2153     state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT);
2154     proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF);
2155     state->server_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_ADDR);
2156     state->server_port = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_PORT);
2157 
2158     if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0
2159           || !alldig(proto_attr)) {
2160           state->client_name = "localhost";
2161 #ifdef AF_INET6
2162           if (proto_info == 0)
2163               proto_info = inet_proto_info();
2164           if (proto_info->sa_family_list[0] == PF_INET6) {
2165               state->client_addr = "::1";
2166               state->client_af = AF_INET6;
2167           } else
2168 #endif
2169           {
2170               state->client_addr = "127.0.0.1";
2171               state->client_af = AF_INET;
2172           }
2173           state->server_addr = state->client_addr;
2174     } else
2175           state->client_af = atoi(proto_attr);
2176     if (state->reverse_name == 0)
2177           state->reverse_name = state->client_name;
2178     /* Compatibility with pre-2.5 queue files. */
2179     if (state->client_port == 0) {
2180           state->client_port = NO_CLIENT_PORT;
2181           state->server_port = state->client_port;
2182     }
2183 }
2184 
2185 /* cleanup_milter_inspect - run message through mail filter */
2186 
cleanup_milter_inspect(CLEANUP_STATE * state,MILTERS * milters)2187 void    cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters)
2188 {
2189     const char *myname = "cleanup_milter";
2190     const char *resp;
2191 
2192     if (msg_verbose)
2193           msg_info("enter %s", myname);
2194 
2195     /*
2196      * Initialize, in case we're called via smtpd(8).
2197      */
2198     if (state->client_name == 0)
2199           cleanup_milter_client_init(state);
2200 
2201     /*
2202      * Prologue: prepare for Milter header/body checks.
2203      */
2204     if (*var_milt_head_checks)
2205           cleanup_milter_header_checks_reinit(state);
2206 
2207     /*
2208      * Process mail filter replies. The reply format is verified by the mail
2209      * filter library.
2210      */
2211     if ((resp = milter_message(milters, state->handle->stream,
2212                                      state->data_offset, state->auto_hdrs)) != 0)
2213           cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
2214 
2215     /*
2216      * Epilogue: finalize Milter header/body checks.
2217      */
2218     if (*var_milt_head_checks)
2219           cleanup_milter_hbc_finish(state);
2220 
2221     if (msg_verbose)
2222           msg_info("leave %s", myname);
2223 }
2224 
2225 /* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */
2226 
cleanup_milter_emul_mail(CLEANUP_STATE * state,MILTERS * milters,const char * addr)2227 void    cleanup_milter_emul_mail(CLEANUP_STATE *state,
2228                                                  MILTERS *milters,
2229                                                  const char *addr)
2230 {
2231     const char *resp;
2232     const char *helo;
2233     const char *argv[2];
2234 
2235     /*
2236      * Per-connection initialization.
2237      */
2238     milter_macro_callback(milters, cleanup_milter_eval, (void *) state);
2239     milter_edit_callback(milters,
2240                                cleanup_add_header, cleanup_upd_header,
2241                                cleanup_ins_header, cleanup_del_header,
2242                                cleanup_chg_from, cleanup_add_rcpt,
2243                                cleanup_add_rcpt_par, cleanup_del_rcpt,
2244                                cleanup_repl_body, (void *) state);
2245     if (state->client_name == 0)
2246           cleanup_milter_client_init(state);
2247 
2248     /*
2249      * Emulate SMTP events.
2250      */
2251     if ((resp = milter_conn_event(milters, state->client_name, state->client_addr,
2252                                     state->client_port, state->client_af)) != 0) {
2253           cleanup_milter_apply(state, "CONNECT", resp);
2254           return;
2255     }
2256 #define PRETEND_ESMTP         1
2257 
2258     if (CLEANUP_MILTER_OK(state)) {
2259           if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0)
2260               helo = state->client_name;
2261           if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0) {
2262               cleanup_milter_apply(state, "EHLO", resp);
2263               return;
2264           }
2265     }
2266     if (CLEANUP_MILTER_OK(state)) {
2267           if (state->milter_ext_from == 0)
2268               state->milter_ext_from = vstring_alloc(100);
2269           /* Sendmail 8.13 does not externalize the null address. */
2270           if (*addr)
2271               quote_821_local(state->milter_ext_from, addr);
2272           else
2273               vstring_strcpy(state->milter_ext_from, addr);
2274           argv[0] = STR(state->milter_ext_from);
2275           argv[1] = 0;
2276           if ((resp = milter_mail_event(milters, argv)) != 0) {
2277               cleanup_milter_apply(state, "MAIL", resp);
2278               return;
2279           }
2280     }
2281 }
2282 
2283 /* cleanup_milter_emul_rcpt - emulate rcpt event */
2284 
cleanup_milter_emul_rcpt(CLEANUP_STATE * state,MILTERS * milters,const char * addr)2285 void    cleanup_milter_emul_rcpt(CLEANUP_STATE *state,
2286                                                  MILTERS *milters,
2287                                                  const char *addr)
2288 {
2289     const char *myname = "cleanup_milter_emul_rcpt";
2290     const char *resp;
2291     const char *argv[2];
2292 
2293     /*
2294      * Sanity check.
2295      */
2296     if (state->client_name == 0)
2297           msg_panic("%s: missing client info initialization", myname);
2298 
2299     /*
2300      * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
2301      * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
2302      * queue record processing, and prevents bounces from being sent.
2303      */
2304     if (state->milter_ext_rcpt == 0)
2305           state->milter_ext_rcpt = vstring_alloc(100);
2306     /* Sendmail 8.13 does not externalize the null address. */
2307     if (*addr)
2308           quote_821_local(state->milter_ext_rcpt, addr);
2309     else
2310           vstring_strcpy(state->milter_ext_rcpt, addr);
2311     argv[0] = STR(state->milter_ext_rcpt);
2312     argv[1] = 0;
2313     if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0
2314           && cleanup_milter_apply(state, "RCPT", resp) != 0) {
2315           msg_warn("%s: milter configuration error: can't reject recipient "
2316                      "in non-smtpd(8) submission", state->queue_id);
2317           msg_warn("%s: message not accepted, try again later", state->queue_id);
2318           CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error");
2319           state->errs |= CLEANUP_STAT_DEFER;
2320     }
2321 }
2322 
2323 /* cleanup_milter_emul_data - emulate data event */
2324 
cleanup_milter_emul_data(CLEANUP_STATE * state,MILTERS * milters)2325 void    cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters)
2326 {
2327     const char *myname = "cleanup_milter_emul_data";
2328     const char *resp;
2329 
2330     /*
2331      * Sanity check.
2332      */
2333     if (state->client_name == 0)
2334           msg_panic("%s: missing client info initialization", myname);
2335 
2336     if ((resp = milter_data_event(milters)) != 0)
2337           cleanup_milter_apply(state, "DATA", resp);
2338 }
2339 
2340 #ifdef TEST
2341 
2342  /*
2343   * Queue file editing driver for regression tests. In this case it is OK to
2344   * report fatal errors after I/O errors.
2345   */
2346 #include <stdio.h>
2347 #include <msg_vstream.h>
2348 #include <vstring_vstream.h>
2349 #include <mail_addr.h>
2350 #include <mail_version.h>
2351 
2352 #undef msg_verbose
2353 
2354 char   *cleanup_path;
2355 VSTRING *cleanup_trace_path;
2356 VSTRING *cleanup_strip_chars;
2357 int     cleanup_comm_canon_flags;
2358 MAPS   *cleanup_comm_canon_maps;
2359 int     cleanup_ext_prop_mask;
2360 ARGV   *cleanup_masq_domains;
2361 int     cleanup_masq_flags;
2362 MAPS   *cleanup_rcpt_bcc_maps;
2363 int     cleanup_rcpt_canon_flags;
2364 MAPS   *cleanup_rcpt_canon_maps;
2365 MAPS   *cleanup_send_bcc_maps;
2366 int     cleanup_send_canon_flags;
2367 MAPS   *cleanup_send_canon_maps;
2368 int     var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
2369 char   *var_empty_addr = DEF_EMPTY_ADDR;
2370 MAPS   *cleanup_virt_alias_maps;
2371 char   *var_milt_daemon_name = "host.example.com";
2372 char   *var_milt_v = DEF_MILT_V;
2373 MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters));
2374 char   *var_milt_head_checks = "";
2375 
2376 /* Dummies to satisfy unused external references. */
2377 
cleanup_masquerade_internal(CLEANUP_STATE * state,VSTRING * addr,ARGV * masq_domains)2378 int     cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains)
2379 {
2380     msg_panic("cleanup_masquerade_internal dummy");
2381 }
2382 
cleanup_rewrite_internal(const char * context,VSTRING * result,const char * addr)2383 int     cleanup_rewrite_internal(const char *context, VSTRING *result,
2384                                                  const char *addr)
2385 {
2386     vstring_strcpy(result, addr);
2387     return (0);
2388 }
2389 
cleanup_map11_internal(CLEANUP_STATE * state,VSTRING * addr,MAPS * maps,int propagate)2390 int     cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
2391                                              MAPS *maps, int propagate)
2392 {
2393     msg_panic("cleanup_map11_internal dummy");
2394 }
2395 
cleanup_map1n_internal(CLEANUP_STATE * state,const char * addr,MAPS * maps,int propagate)2396 ARGV   *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr,
2397                                              MAPS *maps, int propagate)
2398 {
2399     msg_panic("cleanup_map1n_internal dummy");
2400 }
2401 
cleanup_envelope(CLEANUP_STATE * state,int type,const char * buf,ssize_t len)2402 void    cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf,
2403                                        ssize_t len)
2404 {
2405     msg_panic("cleanup_envelope dummy");
2406 }
2407 
usage(void)2408 static void usage(void)
2409 {
2410     msg_warn("usage:");
2411     msg_warn("    verbose on|off");
2412     msg_warn("    open pathname");
2413     msg_warn("    close");
2414     msg_warn("    add_header index name [value]");
2415     msg_warn("    ins_header index name [value]");
2416     msg_warn("    upd_header index name [value]");
2417     msg_warn("    del_header index name");
2418     msg_warn("    chg_from addr parameters");
2419     msg_warn("    add_rcpt addr");
2420     msg_warn("    add_rcpt_par addr parameters");
2421     msg_warn("    del_rcpt addr");
2422     msg_warn("    replbody pathname");
2423     msg_warn("    header_checks type:name");
2424 }
2425 
2426 /* flatten_args - unparse partial command line */
2427 
flatten_args(VSTRING * buf,char ** argv)2428 static void flatten_args(VSTRING *buf, char **argv)
2429 {
2430     char  **cpp;
2431 
2432     VSTRING_RESET(buf);
2433     for (cpp = argv; *cpp; cpp++) {
2434           vstring_strcat(buf, *cpp);
2435           if (cpp[1])
2436               VSTRING_ADDCH(buf, ' ');
2437     }
2438     VSTRING_TERMINATE(buf);
2439 }
2440 
2441 /* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */
2442 
open_queue_file(CLEANUP_STATE * state,const char * path)2443 static void open_queue_file(CLEANUP_STATE *state, const char *path)
2444 {
2445     VSTRING *buf = vstring_alloc(100);
2446     off_t   curr_offset;
2447     int     rec_type;
2448     long    msg_seg_len;
2449     long    data_offset;
2450     long    rcpt_count;
2451     long    qmgr_opts;
2452     const HEADER_OPTS *opts;
2453 
2454     if (state->dst != 0) {
2455           msg_warn("closing %s", cleanup_path);
2456           vstream_fclose(state->dst);
2457           state->dst = 0;
2458           myfree(cleanup_path);
2459           cleanup_path = 0;
2460     }
2461     if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) {
2462           msg_warn("open %s: %m", path);
2463     } else {
2464           var_drop_hdrs = "";
2465           cleanup_path = mystrdup(path);
2466           for (;;) {
2467               if ((curr_offset = vstream_ftell(state->dst)) < 0)
2468                     msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
2469               if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0)
2470                     msg_fatal("file %s: missing SIZE or PTR record", cleanup_path);
2471               if (rec_type == REC_TYPE_SIZE) {
2472                     if (sscanf(STR(buf), "%ld %ld %ld %ld",
2473                                  &msg_seg_len, &data_offset,
2474                                  &rcpt_count, &qmgr_opts) != 4)
2475                         msg_fatal("file %s: bad SIZE record: %s",
2476                                     cleanup_path, STR(buf));
2477                     state->data_offset = data_offset;
2478                     state->xtra_offset = data_offset + msg_seg_len;
2479               } else if (rec_type == REC_TYPE_FROM) {
2480                     state->sender_pt_offset = curr_offset;
2481                     if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE
2482                         && rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR)
2483                         msg_fatal("file %s: missing PTR record after short sender",
2484                                     cleanup_path);
2485                     if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0)
2486                         msg_fatal("file %s: missing END record", cleanup_path);
2487               } else if (rec_type == REC_TYPE_PTR) {
2488                     if (state->data_offset < 0)
2489                         msg_fatal("file %s: missing SIZE record", cleanup_path);
2490                     if (curr_offset < state->data_offset
2491                         || curr_offset > state->xtra_offset) {
2492                         if (state->append_rcpt_pt_offset < 0) {
2493                               state->append_rcpt_pt_offset = curr_offset;
2494                               if (atol(STR(buf)) != 0)
2495                                   msg_fatal("file %s: bad dummy recipient PTR record: %s",
2496                                               cleanup_path, STR(buf));
2497                               if ((state->append_rcpt_pt_target =
2498                                    vstream_ftell(state->dst)) < 0)
2499                                   msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
2500                         } else if (curr_offset > state->xtra_offset
2501                                      && state->append_meta_pt_offset < 0) {
2502                               state->append_meta_pt_offset = curr_offset;
2503                               if (atol(STR(buf)) != 0)
2504                                   msg_fatal("file %s: bad dummy meta PTR record: %s",
2505                                               cleanup_path, STR(buf));
2506                               if ((state->append_meta_pt_target =
2507                                    vstream_ftell(state->dst)) < 0)
2508                                   msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
2509                         }
2510                     } else {
2511                         if (state->append_hdr_pt_offset < 0) {
2512                               state->append_hdr_pt_offset = curr_offset;
2513                               if (atol(STR(buf)) != 0)
2514                                   msg_fatal("file %s: bad dummy header PTR record: %s",
2515                                               cleanup_path, STR(buf));
2516                               if ((state->append_hdr_pt_target =
2517                                    vstream_ftell(state->dst)) < 0)
2518                                   msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
2519                         }
2520                     }
2521               } else if (rec_type == REC_TYPE_NORM && state->hop_count == 0
2522                            && (opts = header_opts_find(STR(buf))) != 0
2523                            && opts->type == HDR_RECEIVED) {
2524                     state->hop_count += 1;
2525                     /* XXX Only the first line of the first Received: header. */
2526                     argv_add(state->auto_hdrs, STR(buf), ARGV_END);
2527               }
2528               if (state->append_rcpt_pt_offset > 0
2529                     && state->append_hdr_pt_offset > 0
2530                     && state->hop_count > 0
2531                     && (rec_type == REC_TYPE_END
2532                         || state->append_meta_pt_offset > 0))
2533                     break;
2534           }
2535           if (msg_verbose) {
2536               msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld",
2537                          (long) state->append_rcpt_pt_offset,
2538                          (long) state->append_rcpt_pt_target);
2539               msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld",
2540                          (long) state->append_hdr_pt_offset,
2541                          (long) state->append_hdr_pt_target);
2542           }
2543     }
2544     vstring_free(buf);
2545 }
2546 
close_queue_file(CLEANUP_STATE * state)2547 static void close_queue_file(CLEANUP_STATE *state)
2548 {
2549     (void) vstream_fclose(state->dst);
2550     state->dst = 0;
2551     myfree(cleanup_path);
2552     cleanup_path = 0;
2553 }
2554 
main(int unused_argc,char ** argv)2555 int     main(int unused_argc, char **argv)
2556 {
2557     VSTRING *inbuf = vstring_alloc(100);
2558     VSTRING *arg_buf = vstring_alloc(100);
2559     char   *bufp;
2560     int     istty = isatty(vstream_fileno(VSTREAM_IN));
2561     CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0);
2562     const char *parens = "{}";
2563 
2564     state->queue_id = mystrdup("NOQUEUE");
2565     state->sender = mystrdup("sender");
2566     state->recip = mystrdup("recipient");
2567     state->client_name = "client_name";
2568     state->client_addr = "client_addr";
2569     state->flags |= CLEANUP_FLAG_FILTER_ALL;
2570 
2571     msg_vstream_init(argv[0], VSTREAM_ERR);
2572     var_line_limit = DEF_LINE_LIMIT;
2573     var_header_limit = DEF_HEADER_LIMIT;
2574     var_enable_orcpt = DEF_ENABLE_ORCPT;
2575     var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM;
2576 
2577     for (;;) {
2578           ARGV   *argv;
2579           ssize_t index;
2580           const char *resp = 0;
2581 
2582           if (istty) {
2583               vstream_printf("- ");
2584               vstream_fflush(VSTREAM_OUT);
2585           }
2586           if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
2587               break;
2588 
2589           bufp = vstring_str(inbuf);
2590           if (!istty) {
2591               vstream_printf("> %s\n", bufp);
2592               vstream_fflush(VSTREAM_OUT);
2593           }
2594           if (*bufp == '#' || *bufp == 0 || allspace(bufp))
2595               continue;
2596           argv = argv_splitq(bufp, " ", parens);
2597           if (argv->argc == 0) {
2598               msg_warn("missing command");
2599           } else if (strcmp(argv->argv[0], "?") == 0) {
2600               usage();
2601           } else if (strcmp(argv->argv[0], "verbose") == 0) {
2602               if (argv->argc != 2) {
2603                     msg_warn("bad verbose argument count: %ld", (long) argv->argc);
2604               } else if (strcmp(argv->argv[1], "on") == 0) {
2605                     msg_verbose = 2;
2606               } else if (strcmp(argv->argv[1], "off") == 0) {
2607                     msg_verbose = 0;
2608               } else {
2609                     msg_warn("bad verbose argument");
2610               }
2611           } else if (strcmp(argv->argv[0], "line_length_limit") == 0) {
2612               if (argv->argc != 2) {
2613                     msg_warn("bad line_length_limit argument count: %ld",
2614                                (long) argv->argc);
2615               } else if (alldig(argv->argv[1]) == 0) {
2616                     msg_warn("bad line_length_limit argument count: %ld",
2617                                (long) argv->argc);
2618               } else if ((var_line_limit = atoi(argv->argv[1])) < DEF_LINE_LIMIT) {
2619                     msg_warn("bad line_length_limit argument");
2620               }
2621           } else if (strcmp(argv->argv[0], "open") == 0) {
2622               if (state->dst != 0) {
2623                     msg_info("closing %s", VSTREAM_PATH(state->dst));
2624                     close_queue_file(state);
2625               }
2626               if (argv->argc != 2) {
2627                     msg_warn("bad open argument count: %ld", (long) argv->argc);
2628               } else {
2629                     open_queue_file(state, argv->argv[1]);
2630               }
2631           } else if (strcmp(argv->argv[0], "enable_original_recipient") == 0) {
2632               if (argv->argc == 1) {
2633                     msg_info("enable_original_recipient: %d", var_enable_orcpt);
2634               } else if (argv->argc != 2) {
2635                     msg_warn("bad enable_original_recipient argument count: %ld",
2636                                (long) argv->argc);
2637               } else if (!alldig(argv->argv[1])) {
2638                     msg_warn("non-numeric enable_original_recipient argument: %s",
2639                                argv->argv[1]);
2640               } else {
2641                     var_enable_orcpt = atoi(argv->argv[1]);
2642               }
2643           } else if (state->dst == 0) {
2644               msg_warn("no open queue file");
2645           } else if (strcmp(argv->argv[0], "close") == 0) {
2646               if (*var_milt_head_checks) {
2647                     cleanup_milter_hbc_finish(state);
2648                     myfree(var_milt_head_checks);
2649                     var_milt_head_checks = "";
2650                     cleanup_milter_header_checks_deinit();
2651               }
2652               close_queue_file(state);
2653           } else if (cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply)) {
2654               /* Postfix libmilter would skip further requests. */
2655               msg_info("ignoring: %s %s %s", argv->argv[0],
2656                          argv->argc > 1 ? argv->argv[1] : "",
2657                          argv->argc > 2 ? argv->argv[2] : "");
2658           } else if (strcmp(argv->argv[0], "add_header") == 0) {
2659               if (argv->argc < 2) {
2660                     msg_warn("bad add_header argument count: %ld",
2661                                (long) argv->argc);
2662               } else {
2663                     flatten_args(arg_buf, argv->argv + 2);
2664                     resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf));
2665               }
2666           } else if (strcmp(argv->argv[0], "ins_header") == 0) {
2667               if (argv->argc < 3) {
2668                     msg_warn("bad ins_header argument count: %ld",
2669                                (long) argv->argc);
2670               } else if ((index = atoi(argv->argv[1])) < 1) {
2671                     msg_warn("bad ins_header index value");
2672               } else {
2673                     flatten_args(arg_buf, argv->argv + 3);
2674                     resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf));
2675               }
2676           } else if (strcmp(argv->argv[0], "upd_header") == 0) {
2677               if (argv->argc < 3) {
2678                     msg_warn("bad upd_header argument count: %ld",
2679                                (long) argv->argc);
2680               } else if ((index = atoi(argv->argv[1])) < 1) {
2681                     msg_warn("bad upd_header index value");
2682               } else {
2683                     flatten_args(arg_buf, argv->argv + 3);
2684                     resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf));
2685               }
2686           } else if (strcmp(argv->argv[0], "del_header") == 0) {
2687               if (argv->argc != 3) {
2688                     msg_warn("bad del_header argument count: %ld",
2689                                (long) argv->argc);
2690               } else if ((index = atoi(argv->argv[1])) < 1) {
2691                     msg_warn("bad del_header index value");
2692               } else {
2693                     cleanup_del_header(state, index, argv->argv[2]);
2694               }
2695           } else if (strcmp(argv->argv[0], "chg_from") == 0) {
2696               if (argv->argc != 3) {
2697                     msg_warn("bad chg_from argument count: %ld", (long) argv->argc);
2698               } else {
2699                     char   *arg = argv->argv[2];
2700                     const char *err;
2701 
2702                     if (*arg == parens[0]
2703                         && (err = extpar(&arg, parens, EXTPAR_FLAG_NONE)) != 0) {
2704                         msg_warn("%s in \"%s\"", err, arg);
2705                     } else {
2706                         cleanup_chg_from(state, argv->argv[1], arg);
2707                     }
2708               }
2709           } else if (strcmp(argv->argv[0], "add_rcpt") == 0) {
2710               if (argv->argc != 2) {
2711                     msg_warn("bad add_rcpt argument count: %ld", (long) argv->argc);
2712               } else {
2713                     cleanup_add_rcpt(state, argv->argv[1]);
2714               }
2715           } else if (strcmp(argv->argv[0], "add_rcpt_par") == 0) {
2716               if (argv->argc != 3) {
2717                     msg_warn("bad add_rcpt_par argument count: %ld",
2718                                (long) argv->argc);
2719               } else {
2720                     cleanup_add_rcpt_par(state, argv->argv[1], argv->argv[2]);
2721               }
2722           } else if (strcmp(argv->argv[0], "del_rcpt") == 0) {
2723               if (argv->argc != 2) {
2724                     msg_warn("bad del_rcpt argument count: %ld", (long) argv->argc);
2725               } else {
2726                     cleanup_del_rcpt(state, argv->argv[1]);
2727               }
2728           } else if (strcmp(argv->argv[0], "replbody") == 0) {
2729               if (argv->argc != 2) {
2730                     msg_warn("bad replbody argument count: %ld", (long) argv->argc);
2731               } else {
2732                     VSTREAM *fp;
2733                     VSTRING *buf;
2734 
2735                     if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) {
2736                         msg_warn("open %s file: %m", argv->argv[1]);
2737                     } else {
2738                         buf = vstring_alloc(100);
2739                         cleanup_repl_body(state, MILTER_BODY_START,
2740                                               REC_TYPE_NORM, buf);
2741                         while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
2742                               cleanup_repl_body(state, MILTER_BODY_LINE,
2743                                                     REC_TYPE_NORM, buf);
2744                         cleanup_repl_body(state, MILTER_BODY_END,
2745                                               REC_TYPE_NORM, buf);
2746                         vstring_free(buf);
2747                         vstream_fclose(fp);
2748                     }
2749               }
2750           } else if (strcmp(argv->argv[0], "header_checks") == 0) {
2751               if (argv->argc != 2) {
2752                     msg_warn("bad header_checks argument count: %ld",
2753                                (long) argv->argc);
2754               } else if (*var_milt_head_checks) {
2755                     msg_warn("can't change header checks");
2756               } else {
2757                     var_milt_head_checks = mystrdup(argv->argv[1]);
2758                     cleanup_milter_header_checks_init();
2759               }
2760           } else if (strcmp(argv->argv[0], "sender_bcc_maps") == 0) {
2761               if (argv->argc != 2) {
2762                     msg_warn("bad sender_bcc_maps argument count: %ld",
2763                                (long) argv->argc);
2764               } else {
2765                     if (cleanup_send_bcc_maps)
2766                         maps_free(cleanup_send_bcc_maps);
2767                     cleanup_send_bcc_maps =
2768                         maps_create("sender_bcc_maps", argv->argv[1],
2769                                         DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
2770                                         | DICT_FLAG_UTF8_REQUEST);
2771                     state->flags |= CLEANUP_FLAG_BCC_OK;
2772                     var_rcpt_delim = "";
2773               }
2774           } else {
2775               msg_warn("bad command: %s", argv->argv[0]);
2776           }
2777           argv_free(argv);
2778           if (resp)
2779               cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
2780     }
2781     vstring_free(inbuf);
2782     vstring_free(arg_buf);
2783     if (state->append_meta_pt_offset >= 0) {
2784           if (state->flags)
2785               msg_info("flags = %s", cleanup_strflags(state->flags));
2786           if (state->errs)
2787               msg_info("errs = %s", cleanup_strerror(state->errs));
2788     }
2789     cleanup_state_free(state);
2790     if (*var_milt_head_checks)
2791           myfree(var_milt_head_checks);
2792 
2793     return (0);
2794 }
2795 
2796 #endif
2797