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