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