summaryrefslogtreecommitdiffstats
path: root/src/cleanup
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/cleanup/.indent.pro1
-rw-r--r--src/cleanup/.printfck25
-rw-r--r--src/cleanup/Makefile.in1320
-rw-r--r--src/cleanup/bug1.filebin0 -> 1258 bytes
-rwxr-xr-xsrc/cleanup/bug1.file.refbin0 -> 1279 bytes
-rw-r--r--src/cleanup/bug1.in41
-rw-r--r--src/cleanup/bug1.ref56
-rw-r--r--src/cleanup/bug1.text.ref46
-rw-r--r--src/cleanup/bug2.filebin0 -> 573 bytes
-rw-r--r--src/cleanup/bug2.in37
-rw-r--r--src/cleanup/bug2.ref30
-rw-r--r--src/cleanup/bug2.text.ref22
-rw-r--r--src/cleanup/bug3.filebin0 -> 565 bytes
-rw-r--r--src/cleanup/bug3.in29
-rw-r--r--src/cleanup/bug3.ref29
-rw-r--r--src/cleanup/bug3.text.ref21
-rw-r--r--src/cleanup/cleanup.c635
-rw-r--r--src/cleanup/cleanup.h372
-rw-r--r--src/cleanup/cleanup_addr.c286
-rw-r--r--src/cleanup/cleanup_api.c395
-rw-r--r--src/cleanup/cleanup_body_edit.c236
-rw-r--r--src/cleanup/cleanup_bounce.c257
-rw-r--r--src/cleanup/cleanup_envelope.c502
-rw-r--r--src/cleanup/cleanup_extracted.c328
-rw-r--r--src/cleanup/cleanup_final.c78
-rw-r--r--src/cleanup/cleanup_init.c483
-rw-r--r--src/cleanup/cleanup_map11.c183
-rw-r--r--src/cleanup/cleanup_map1n.c182
-rw-r--r--src/cleanup/cleanup_masq.ref66
-rw-r--r--src/cleanup/cleanup_masquerade.c239
-rw-r--r--src/cleanup/cleanup_message.c1116
-rw-r--r--src/cleanup/cleanup_milter.c2748
-rw-r--r--src/cleanup/cleanup_milter.in142
-rw-r--r--src/cleanup/cleanup_milter.in10a11
-rw-r--r--src/cleanup/cleanup_milter.in10b12
-rw-r--r--src/cleanup/cleanup_milter.in10c14
-rw-r--r--src/cleanup/cleanup_milter.in10d14
-rw-r--r--src/cleanup/cleanup_milter.in10e13
-rw-r--r--src/cleanup/cleanup_milter.in1110
-rw-r--r--src/cleanup/cleanup_milter.in1222
-rw-r--r--src/cleanup/cleanup_milter.in13a22
-rw-r--r--src/cleanup/cleanup_milter.in13b8
-rw-r--r--src/cleanup/cleanup_milter.in13c9
-rw-r--r--src/cleanup/cleanup_milter.in13d9
-rw-r--r--src/cleanup/cleanup_milter.in13e10
-rw-r--r--src/cleanup/cleanup_milter.in13f10
-rw-r--r--src/cleanup/cleanup_milter.in13g11
-rw-r--r--src/cleanup/cleanup_milter.in13h8
-rw-r--r--src/cleanup/cleanup_milter.in13i9
-rw-r--r--src/cleanup/cleanup_milter.in14a7
-rw-r--r--src/cleanup/cleanup_milter.in14b7
-rw-r--r--src/cleanup/cleanup_milter.in14c7
-rw-r--r--src/cleanup/cleanup_milter.in14d7
-rw-r--r--src/cleanup/cleanup_milter.in14e7
-rw-r--r--src/cleanup/cleanup_milter.in14f7
-rw-r--r--src/cleanup/cleanup_milter.in14g7
-rw-r--r--src/cleanup/cleanup_milter.in15a7
-rw-r--r--src/cleanup/cleanup_milter.in15b7
-rw-r--r--src/cleanup/cleanup_milter.in15c7
-rw-r--r--src/cleanup/cleanup_milter.in15d7
-rw-r--r--src/cleanup/cleanup_milter.in15e7
-rw-r--r--src/cleanup/cleanup_milter.in15f7
-rw-r--r--src/cleanup/cleanup_milter.in15g7
-rw-r--r--src/cleanup/cleanup_milter.in15h11
-rw-r--r--src/cleanup/cleanup_milter.in15i11
-rw-r--r--src/cleanup/cleanup_milter.in16a10
-rw-r--r--src/cleanup/cleanup_milter.in16b12
-rw-r--r--src/cleanup/cleanup_milter.in228
-rw-r--r--src/cleanup/cleanup_milter.in360
-rw-r--r--src/cleanup/cleanup_milter.in4a9
-rw-r--r--src/cleanup/cleanup_milter.in4b9
-rw-r--r--src/cleanup/cleanup_milter.in4c12
-rw-r--r--src/cleanup/cleanup_milter.in519
-rw-r--r--src/cleanup/cleanup_milter.in6a5
-rw-r--r--src/cleanup/cleanup_milter.in6b6
-rw-r--r--src/cleanup/cleanup_milter.in6c7
-rw-r--r--src/cleanup/cleanup_milter.in77
-rw-r--r--src/cleanup/cleanup_milter.in87
-rw-r--r--src/cleanup/cleanup_milter.in97
-rw-r--r--src/cleanup/cleanup_milter.ref156
-rw-r--r--src/cleanup/cleanup_milter.ref10a53
-rw-r--r--src/cleanup/cleanup_milter.ref10b53
-rw-r--r--src/cleanup/cleanup_milter.ref10c83
-rw-r--r--src/cleanup/cleanup_milter.ref10d53
-rw-r--r--src/cleanup/cleanup_milter.ref10e89
-rw-r--r--src/cleanup/cleanup_milter.ref1166
-rw-r--r--src/cleanup/cleanup_milter.ref1231
-rw-r--r--src/cleanup/cleanup_milter.ref13a31
-rw-r--r--src/cleanup/cleanup_milter.ref13b27
-rw-r--r--src/cleanup/cleanup_milter.ref13c29
-rw-r--r--src/cleanup/cleanup_milter.ref13d39
-rw-r--r--src/cleanup/cleanup_milter.ref13e30
-rw-r--r--src/cleanup/cleanup_milter.ref13f32
-rw-r--r--src/cleanup/cleanup_milter.ref13g34
-rw-r--r--src/cleanup/cleanup_milter.ref13h29
-rw-r--r--src/cleanup/cleanup_milter.ref13i33
-rw-r--r--src/cleanup/cleanup_milter.ref14a12
-rw-r--r--src/cleanup/cleanup_milter.ref14a227
-rw-r--r--src/cleanup/cleanup_milter.ref14b12
-rw-r--r--src/cleanup/cleanup_milter.ref14b229
-rw-r--r--src/cleanup/cleanup_milter.ref14c11
-rw-r--r--src/cleanup/cleanup_milter.ref14c229
-rw-r--r--src/cleanup/cleanup_milter.ref14d12
-rw-r--r--src/cleanup/cleanup_milter.ref14d225
-rw-r--r--src/cleanup/cleanup_milter.ref14e12
-rw-r--r--src/cleanup/cleanup_milter.ref14e227
-rw-r--r--src/cleanup/cleanup_milter.ref14f12
-rw-r--r--src/cleanup/cleanup_milter.ref14f228
-rw-r--r--src/cleanup/cleanup_milter.ref14g12
-rw-r--r--src/cleanup/cleanup_milter.ref14g227
-rw-r--r--src/cleanup/cleanup_milter.ref15a12
-rw-r--r--src/cleanup/cleanup_milter.ref15a227
-rw-r--r--src/cleanup/cleanup_milter.ref15b12
-rw-r--r--src/cleanup/cleanup_milter.ref15b229
-rw-r--r--src/cleanup/cleanup_milter.ref15c11
-rw-r--r--src/cleanup/cleanup_milter.ref15c229
-rw-r--r--src/cleanup/cleanup_milter.ref15d12
-rw-r--r--src/cleanup/cleanup_milter.ref15d225
-rw-r--r--src/cleanup/cleanup_milter.ref15e12
-rw-r--r--src/cleanup/cleanup_milter.ref15e227
-rw-r--r--src/cleanup/cleanup_milter.ref15f11
-rw-r--r--src/cleanup/cleanup_milter.ref15f230
-rw-r--r--src/cleanup/cleanup_milter.ref15g12
-rw-r--r--src/cleanup/cleanup_milter.ref15g230
-rw-r--r--src/cleanup/cleanup_milter.ref15h13
-rw-r--r--src/cleanup/cleanup_milter.ref15h227
-rw-r--r--src/cleanup/cleanup_milter.ref15i13
-rw-r--r--src/cleanup/cleanup_milter.ref15i229
-rw-r--r--src/cleanup/cleanup_milter.ref16a13
-rw-r--r--src/cleanup/cleanup_milter.ref16a237
-rw-r--r--src/cleanup/cleanup_milter.ref16b11
-rw-r--r--src/cleanup/cleanup_milter.ref16b242
-rw-r--r--src/cleanup/cleanup_milter.ref236
-rw-r--r--src/cleanup/cleanup_milter.ref350
-rw-r--r--src/cleanup/cleanup_milter.ref459
-rw-r--r--src/cleanup/cleanup_milter.ref529
-rw-r--r--src/cleanup/cleanup_milter.ref6a28
-rw-r--r--src/cleanup/cleanup_milter.ref6b31
-rw-r--r--src/cleanup/cleanup_milter.ref6c33
-rw-r--r--src/cleanup/cleanup_milter.ref732
-rw-r--r--src/cleanup/cleanup_milter.ref834
-rw-r--r--src/cleanup/cleanup_milter.ref933
-rw-r--r--src/cleanup/cleanup_milter.reg14a1
-rw-r--r--src/cleanup/cleanup_milter.reg14b1
-rw-r--r--src/cleanup/cleanup_milter.reg14c1
-rw-r--r--src/cleanup/cleanup_milter.reg14d1
-rw-r--r--src/cleanup/cleanup_milter.reg14e1
-rw-r--r--src/cleanup/cleanup_milter.reg14f1
-rw-r--r--src/cleanup/cleanup_milter.reg14g1
-rw-r--r--src/cleanup/cleanup_milter.reg15a1
-rw-r--r--src/cleanup/cleanup_milter.reg15b1
-rw-r--r--src/cleanup/cleanup_milter.reg15c1
-rw-r--r--src/cleanup/cleanup_milter.reg15d1
-rw-r--r--src/cleanup/cleanup_milter.reg15e1
-rw-r--r--src/cleanup/cleanup_milter.reg15f1
-rw-r--r--src/cleanup/cleanup_milter.reg15g1
-rw-r--r--src/cleanup/cleanup_milter.reg15h2
-rw-r--r--src/cleanup/cleanup_milter.reg15i2
-rw-r--r--src/cleanup/cleanup_milter.reg16a2
-rw-r--r--src/cleanup/cleanup_out.c225
-rw-r--r--src/cleanup/cleanup_out_recipient.c265
-rw-r--r--src/cleanup/cleanup_region.c232
-rw-r--r--src/cleanup/cleanup_rewrite.c123
-rw-r--r--src/cleanup/cleanup_state.c200
-rw-r--r--src/cleanup/loremipsum28
-rw-r--r--src/cleanup/loremipsum257
-rw-r--r--src/cleanup/test-queue-filebin0 -> 1258 bytes
-rw-r--r--src/cleanup/test-queue-file10bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file11bin0 -> 975 bytes
-rw-r--r--src/cleanup/test-queue-file12bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13abin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13bbin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13cbin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13dbin0 -> 975 bytes
-rw-r--r--src/cleanup/test-queue-file13ebin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13fbin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13gbin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13hbin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file13ibin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file14bin0 -> 642 bytes
-rw-r--r--src/cleanup/test-queue-file15bin0 -> 641 bytes
-rw-r--r--src/cleanup/test-queue-file16bin0 -> 654 bytes
-rw-r--r--src/cleanup/test-queue-file2bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file3bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file4bin0 -> 1258 bytes
-rw-r--r--src/cleanup/test-queue-file5bin0 -> 617 bytes
-rw-r--r--src/cleanup/test-queue-file6bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file7bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file8bin0 -> 573 bytes
-rw-r--r--src/cleanup/test-queue-file9bin0 -> 573 bytes
190 files changed, 13157 insertions, 0 deletions
diff --git a/src/cleanup/.indent.pro b/src/cleanup/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/cleanup/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/cleanup/.printfck b/src/cleanup/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/cleanup/.printfck
@@ -0,0 +1,25 @@
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
diff --git a/src/cleanup/Makefile.in b/src/cleanup/Makefile.in
new file mode 100644
index 0000000..546aae1
--- /dev/null
+++ b/src/cleanup/Makefile.in
@@ -0,0 +1,1320 @@
+SHELL = /bin/sh
+SRCS = cleanup.c cleanup_out.c cleanup_envelope.c cleanup_message.c \
+ cleanup_extracted.c cleanup_state.c cleanup_rewrite.c \
+ cleanup_map11.c cleanup_map1n.c cleanup_masquerade.c \
+ cleanup_out_recipient.c cleanup_init.c cleanup_api.c \
+ cleanup_addr.c cleanup_bounce.c cleanup_milter.c \
+ cleanup_body_edit.c cleanup_region.c cleanup_final.c
+OBJS = cleanup.o cleanup_out.o cleanup_envelope.o cleanup_message.o \
+ cleanup_extracted.o cleanup_state.o cleanup_rewrite.o \
+ cleanup_map11.o cleanup_map1n.o cleanup_masquerade.o \
+ cleanup_out_recipient.o cleanup_init.o cleanup_api.o \
+ cleanup_addr.o cleanup_bounce.o cleanup_milter.o \
+ cleanup_body_edit.o cleanup_region.o cleanup_final.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG= cleanup_masquerade cleanup_milter
+PROG = cleanup
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/libmilter.a \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile > printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+cleanup_masquerade: cleanup_masquerade.o
+ mv cleanup_masquerade.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS)
+ mv junk cleanup_masquerade.o
+
+CLEANUP_MILTER_OBJS = cleanup_state.o cleanup_out.o cleanup_addr.o \
+ cleanup_out_recipient.o cleanup_body_edit.o cleanup_region.o
+cleanup_milter: cleanup_milter.o $(CLEANUP_MILTER_OBJS) $(LIBS)
+ mv cleanup_milter.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(CLEANUP_MILTER_OBJS) $(LIBS) $(SYSLIBS)
+ mv junk cleanup_milter.o
+
+tests: cleanup_masquerade_test milter_tests
+
+milter_tests: cleanup_milter_test bug_tests \
+ cleanup_milter_test2 cleanup_milter_test3 cleanup_milter_test4 \
+ cleanup_milter_test5 cleanup_milter_test6 cleanup_milter_test7 \
+ cleanup_milter_test8 cleanup_milter_test9 cleanup_milter_test10a \
+ cleanup_milter_test10b cleanup_milter_test10c cleanup_milter_test10d \
+ cleanup_milter_test10e cleanup_milter_test11 cleanup_milter_test12 \
+ cleanup_milter_test13a cleanup_milter_test13b cleanup_milter_test13c \
+ cleanup_milter_test13d cleanup_milter_test13e cleanup_milter_test13f \
+ cleanup_milter_test13g cleanup_milter_test13h cleanup_milter_test13i \
+ cleanup_milter_test14a cleanup_milter_test14b cleanup_milter_test14c \
+ cleanup_milter_test14d cleanup_milter_test14e cleanup_milter_test14f \
+ cleanup_milter_test14g \
+ cleanup_milter_test15a cleanup_milter_test15b cleanup_milter_test15c \
+ cleanup_milter_test15d cleanup_milter_test15e cleanup_milter_test15f \
+ cleanup_milter_test15g cleanup_milter_test15h cleanup_milter_test15i \
+ cleanup_milter_test16a cleanup_milter_test16b
+
+root_tests:
+
+cleanup_masquerade_test: cleanup_masquerade cleanup_masq.ref
+ rm -f cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' A.B.C,B.C xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' a.b.c,b.c xxx@AA.A.B.C >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade 'xxx' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade 'yyy' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' !a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' !a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' a.b.c,b.c xxx@aaa.b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade '' a.b.c,b.c xxx@b.c >>cleanup_masq.tmp
+ $(SHLIB_ENV) ./cleanup_masquerade 'fail:whatever' xy xxx@b.c >>cleanup_masq.tmp
+ diff cleanup_masq.ref cleanup_masq.tmp
+ rm -f cleanup_masq.tmp
+
+bug_tests: bug1_test bug2_test bug3_test
+
+../postcat/postcat:
+ cd ../postcat; make
+
+bug1_test: cleanup_milter bug1.file bug1.in bug1.ref bug1.text.ref \
+ ../postcat/postcat
+ cp bug1.file bug1.file.tmp
+ chmod u+w bug1.file.tmp
+ $(SHLIB_ENV) ./cleanup_milter <bug1.in
+ $(SHLIB_ENV) ../postcat/postcat -ov bug1.file.tmp 2>/dev/null >bug1.tmp
+ diff bug1.ref bug1.tmp
+ $(SHLIB_ENV) ../postcat/postcat bug1.file.tmp 2>/dev/null >bug1.tmp
+ diff bug1.text.ref bug1.tmp
+ rm -f bug1.file.tmp bug1.tmp
+
+bug2_test: cleanup_milter bug2.file bug2.in bug2.ref bug2.text.ref \
+ ../postcat/postcat
+ cp bug2.file bug2.file.tmp
+ chmod u+w bug2.file.tmp
+ $(SHLIB_ENV) ./cleanup_milter <bug2.in
+ $(SHLIB_ENV) ../postcat/postcat -ov bug2.file.tmp 2>/dev/null >bug2.tmp
+ diff bug2.ref bug2.tmp
+ $(SHLIB_ENV) ../postcat/postcat bug2.file.tmp 2>/dev/null >bug2.tmp
+ diff bug2.text.ref bug2.tmp
+ rm -f bug2.file.tmp bug2.tmp
+
+bug3_test: cleanup_milter bug3.file bug3.in bug3.ref bug3.text.ref \
+ ../postcat/postcat
+ cp bug3.file bug3.file.tmp
+ chmod u+w bug3.file.tmp
+ $(SHLIB_ENV) ./cleanup_milter <bug3.in
+ $(SHLIB_ENV) ../postcat/postcat -ov bug3.file.tmp 2>/dev/null >bug3.tmp
+ diff bug3.ref bug3.tmp
+ $(SHLIB_ENV) ../postcat/postcat bug3.file.tmp 2>/dev/null >bug3.tmp
+ diff bug3.text.ref bug3.tmp
+ rm -f bug3.file.tmp bug3.tmp
+
+# Test queue file editing routines.
+
+cleanup_milter_test: cleanup_milter test-queue-file cleanup_milter.in1 \
+ cleanup_milter.ref1 ../postcat/postcat
+ cp test-queue-file test-queue-file.tmp
+ chmod u+w test-queue-file.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref1 cleanup_milter.tmp
+ rm -f test-queue-file.tmp cleanup_milter.tmp
+
+cleanup_milter_test2: cleanup_milter test-queue-file2 cleanup_milter.in2 \
+ cleanup_milter.ref2 ../postcat/postcat
+ cp test-queue-file2 test-queue-file2.tmp
+ chmod u+w test-queue-file2.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in2
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file2.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref2 cleanup_milter.tmp
+ rm -f test-queue-file2.tmp cleanup_milter.tmp
+
+cleanup_milter_test3: cleanup_milter test-queue-file3 cleanup_milter.in3 \
+ cleanup_milter.ref3 ../postcat/postcat
+ cp test-queue-file3 test-queue-file3.tmp
+ chmod u+w test-queue-file3.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in3
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file3.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref3 cleanup_milter.tmp
+ rm -f test-queue-file3.tmp cleanup_milter.tmp
+
+cleanup_milter_test4: cleanup_milter test-queue-file4 cleanup_milter.in4a \
+ cleanup_milter.in4b cleanup_milter.in4c cleanup_milter.ref4 \
+ test-queue-file4 ../postcat/postcat
+ cp test-queue-file4 test-queue-file4.tmp
+ chmod u+w test-queue-file4.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in4a
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file4.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref4 cleanup_milter.tmp
+ cp test-queue-file4 test-queue-file4.tmp
+ chmod u+w test-queue-file4.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in4b
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file4.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref4 cleanup_milter.tmp
+ cp test-queue-file4 test-queue-file4.tmp
+ chmod u+w test-queue-file4.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in4c
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file4.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref4 cleanup_milter.tmp
+ rm -f test-queue-file4.tmp cleanup_milter.tmp
+
+cleanup_milter_test5: cleanup_milter test-queue-file5 cleanup_milter.in5 \
+ cleanup_milter.ref5 ../postcat/postcat
+ cp test-queue-file5 test-queue-file5.tmp
+ chmod u+w test-queue-file5.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in5
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file5.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref5 cleanup_milter.tmp
+ rm -f test-queue-file5.tmp cleanup_milter.tmp
+
+cleanup_milter_test6: cleanup_milter_test6a cleanup_milter_test6b cleanup_milter_test6c
+ rm -f test-queue-file6.tmp cleanup_milter.tmp
+
+cleanup_milter_test6a: cleanup_milter test-queue-file6 cleanup_milter.in6a \
+ cleanup_milter.ref6a test-queue-file6 ../postcat/postcat
+ cp test-queue-file6 test-queue-file6.tmp
+ chmod u+w test-queue-file6.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in6a
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file6.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref6a cleanup_milter.tmp
+ rm -f test-queue-file6.tmp cleanup_milter.tmp
+
+cleanup_milter_test6b: cleanup_milter test-queue-file6 cleanup_milter.in6b \
+ cleanup_milter.ref6b ../postcat/postcat
+ cp test-queue-file6 test-queue-file6.tmp
+ chmod u+w test-queue-file6.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in6b
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file6.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref6b cleanup_milter.tmp
+ rm -f test-queue-file6.tmp cleanup_milter.tmp
+
+cleanup_milter_test6c: cleanup_milter test-queue-file6 cleanup_milter.in6c \
+ cleanup_milter.ref6c ../postcat/postcat
+ cp test-queue-file6 test-queue-file6.tmp
+ chmod u+w test-queue-file6.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in6c
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file6.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref6c cleanup_milter.tmp
+ rm -f test-queue-file6.tmp cleanup_milter.tmp
+
+cleanup_milter_test7: cleanup_milter test-queue-file7 cleanup_milter.in7 \
+ cleanup_milter.ref7 ../postcat/postcat
+ cp test-queue-file7 test-queue-file7.tmp
+ chmod u+w test-queue-file7.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in7
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file7.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref7 cleanup_milter.tmp
+ rm -f test-queue-file7.tmp cleanup_milter.tmp
+
+cleanup_milter_test8: cleanup_milter test-queue-file8 cleanup_milter.in8 \
+ cleanup_milter.ref8 ../postcat/postcat
+ cp test-queue-file8 test-queue-file8.tmp
+ chmod u+w test-queue-file8.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in8
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file8.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref8 cleanup_milter.tmp
+ rm -f test-queue-file8.tmp cleanup_milter.tmp
+
+cleanup_milter_test9: cleanup_milter test-queue-file9 cleanup_milter.in9 \
+ cleanup_milter.ref9 ../postcat/postcat
+ cp test-queue-file9 test-queue-file9.tmp
+ chmod u+w test-queue-file9.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in9
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file9.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref9 cleanup_milter.tmp
+ rm -f test-queue-file9.tmp cleanup_milter.tmp
+
+cleanup_milter_test10a: cleanup_milter test-queue-file10 cleanup_milter.in10a \
+ cleanup_milter.ref10a ../postcat/postcat
+ cp test-queue-file10 test-queue-file10.tmp
+ chmod u+w test-queue-file10.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in10a
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file10.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref10a cleanup_milter.tmp
+ rm -f test-queue-file10.tmp cleanup_milter.tmp
+
+cleanup_milter_test10b: cleanup_milter test-queue-file10 cleanup_milter.in10b \
+ cleanup_milter.ref10b ../postcat/postcat
+ cp test-queue-file10 test-queue-file10.tmp
+ chmod u+w test-queue-file10.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in10b
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file10.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref10b cleanup_milter.tmp
+ rm -f test-queue-file10.tmp cleanup_milter.tmp
+
+cleanup_milter_test10c: cleanup_milter test-queue-file10 cleanup_milter.in10c \
+ cleanup_milter.ref10c ../postcat/postcat
+ cp test-queue-file10 test-queue-file10.tmp
+ chmod u+w test-queue-file10.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in10c
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file10.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref10c cleanup_milter.tmp
+ rm -f test-queue-file10.tmp cleanup_milter.tmp
+
+cleanup_milter_test10d: cleanup_milter test-queue-file10 cleanup_milter.in10c \
+ cleanup_milter.ref10d ../postcat/postcat
+ cp test-queue-file10 test-queue-file10.tmp
+ chmod u+w test-queue-file10.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in10d
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file10.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref10d cleanup_milter.tmp
+ rm -f test-queue-file10.tmp cleanup_milter.tmp
+
+cleanup_milter_test10e: cleanup_milter test-queue-file10 cleanup_milter.in10c \
+ cleanup_milter.ref10e ../postcat/postcat
+ cp test-queue-file10 test-queue-file10.tmp
+ chmod u+w test-queue-file10.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in10e
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file10.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref10e cleanup_milter.tmp
+ rm -f test-queue-file10.tmp cleanup_milter.tmp
+
+cleanup_milter_test11: cleanup_milter test-queue-file11 cleanup_milter.in11 \
+ cleanup_milter.ref11 ../postcat/postcat
+ cp test-queue-file11 test-queue-file11.tmp
+ chmod u+w test-queue-file11.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in11
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file11.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref11 cleanup_milter.tmp
+ rm -f test-queue-file11.tmp cleanup_milter.tmp
+
+cleanup_milter_test12: cleanup_milter test-queue-file12 cleanup_milter.in12 \
+ cleanup_milter.ref12 ../postcat/postcat
+ cp test-queue-file12 test-queue-file12.tmp
+ chmod u+w test-queue-file12.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in12
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file12.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref12 cleanup_milter.tmp
+ rm -f test-queue-file12.tmp cleanup_milter.tmp
+
+cleanup_milter_test13a: cleanup_milter test-queue-file13a cleanup_milter.in13a \
+ cleanup_milter.ref13a ../postcat/postcat
+ cp test-queue-file13a test-queue-file13a.tmp
+ chmod u+w test-queue-file13a.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13a
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13a.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13a cleanup_milter.tmp
+ rm -f test-queue-file13a.tmp cleanup_milter.tmp
+
+cleanup_milter_test13b: cleanup_milter test-queue-file13b cleanup_milter.in13b \
+ cleanup_milter.ref13b ../postcat/postcat
+ cp test-queue-file13b test-queue-file13b.tmp
+ chmod u+w test-queue-file13b.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13b
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13b.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13b cleanup_milter.tmp
+ rm -f test-queue-file13b.tmp cleanup_milter.tmp
+
+cleanup_milter_test13c: cleanup_milter test-queue-file13c cleanup_milter.in13c \
+ cleanup_milter.ref13c ../postcat/postcat
+ cp test-queue-file13c test-queue-file13c.tmp
+ chmod u+w test-queue-file13c.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13c
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13c.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13c cleanup_milter.tmp
+ rm -f test-queue-file13c.tmp cleanup_milter.tmp
+
+cleanup_milter_test13d: cleanup_milter test-queue-file13d cleanup_milter.in13d \
+ cleanup_milter.ref13d ../postcat/postcat
+ cp test-queue-file13d test-queue-file13d.tmp
+ chmod u+w test-queue-file13d.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13d
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13d.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13d cleanup_milter.tmp
+ rm -f test-queue-file13d.tmp cleanup_milter.tmp
+
+cleanup_milter_test13e: cleanup_milter test-queue-file13e cleanup_milter.in13e \
+ cleanup_milter.ref13e ../postcat/postcat
+ cp test-queue-file13e test-queue-file13e.tmp
+ chmod u+w test-queue-file13e.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13e
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13e.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13e cleanup_milter.tmp
+ rm -f test-queue-file13e.tmp cleanup_milter.tmp
+
+cleanup_milter_test13g: cleanup_milter test-queue-file13g cleanup_milter.in13g \
+ cleanup_milter.ref13g ../postcat/postcat
+ cp test-queue-file13g test-queue-file13g.tmp
+ chmod u+w test-queue-file13g.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13g
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13g.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13g cleanup_milter.tmp
+ rm -f test-queue-file13g.tmp cleanup_milter.tmp
+
+cleanup_milter_test13h: cleanup_milter test-queue-file13h cleanup_milter.in13h \
+ cleanup_milter.ref13h ../postcat/postcat
+ cp test-queue-file13h test-queue-file13h.tmp
+ chmod u+w test-queue-file13h.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13h
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13h.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13h cleanup_milter.tmp
+ rm -f test-queue-file13h.tmp cleanup_milter.tmp
+
+cleanup_milter_test13i: cleanup_milter test-queue-file13i cleanup_milter.in13i \
+ cleanup_milter.ref13i ../postcat/postcat
+ cp test-queue-file13i test-queue-file13i.tmp
+ chmod u+w test-queue-file13i.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13i
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13i.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13i cleanup_milter.tmp
+ rm -f test-queue-file13i.tmp cleanup_milter.tmp
+
+cleanup_milter_test13f: cleanup_milter test-queue-file13f cleanup_milter.in13f \
+ cleanup_milter.ref13f ../postcat/postcat
+ cp test-queue-file13f test-queue-file13f.tmp
+ chmod u+w test-queue-file13f.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in13f
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file13f.tmp 2>/dev/null >cleanup_milter.tmp
+ diff cleanup_milter.ref13f cleanup_milter.tmp
+ rm -f test-queue-file13f.tmp cleanup_milter.tmp
+
+cleanup_milter_test14a: cleanup_milter test-queue-file14 cleanup_milter.in14a \
+ cleanup_milter.ref14a1 ../postcat/postcat cleanup_milter.ref14a2 \
+ cleanup_milter.reg14a
+ cp test-queue-file14 test-queue-file14a.tmp
+ chmod u+w test-queue-file14a.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14a.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14a2 cleanup_milter.tmp2
+ rm -f test-queue-file14a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14b: cleanup_milter test-queue-file14 cleanup_milter.in14b \
+ cleanup_milter.ref14b1 ../postcat/postcat cleanup_milter.ref14b2 \
+ cleanup_milter.reg14b
+ cp test-queue-file14 test-queue-file14b.tmp
+ chmod u+w test-queue-file14b.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14b.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14b2 cleanup_milter.tmp2
+ rm -f test-queue-file14b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14c: cleanup_milter test-queue-file14 cleanup_milter.in14c \
+ cleanup_milter.ref14c1 ../postcat/postcat cleanup_milter.ref14c2 \
+ cleanup_milter.reg14c
+ cp test-queue-file14 test-queue-file14c.tmp
+ chmod u+w test-queue-file14c.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14c.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14c2 cleanup_milter.tmp2
+ rm -f test-queue-file14c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14d: cleanup_milter test-queue-file14 cleanup_milter.in14d \
+ cleanup_milter.ref14d1 ../postcat/postcat cleanup_milter.ref14d2 \
+ cleanup_milter.reg14d
+ cp test-queue-file14 test-queue-file14d.tmp
+ chmod u+w test-queue-file14d.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14d.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14d2 cleanup_milter.tmp2
+ rm -f test-queue-file14d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14e: cleanup_milter test-queue-file14 cleanup_milter.in14e \
+ cleanup_milter.ref14e1 ../postcat/postcat cleanup_milter.ref14e2 \
+ cleanup_milter.reg14e
+ cp test-queue-file14 test-queue-file14e.tmp
+ chmod u+w test-queue-file14e.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14e 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14e1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14e.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14e2 cleanup_milter.tmp2
+ rm -f test-queue-file14e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14f: cleanup_milter test-queue-file14 cleanup_milter.in14f \
+ cleanup_milter.ref14f1 ../postcat/postcat cleanup_milter.ref14f2 \
+ cleanup_milter.reg14f
+ cp test-queue-file14 test-queue-file14f.tmp
+ chmod u+w test-queue-file14f.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14f 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14f1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14f.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14f2 cleanup_milter.tmp2
+ rm -f test-queue-file14f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test14g: cleanup_milter test-queue-file14 cleanup_milter.in14g \
+ cleanup_milter.ref14g1 ../postcat/postcat cleanup_milter.ref14g2 \
+ cleanup_milter.reg14g
+ cp test-queue-file14 test-queue-file14g.tmp
+ chmod u+w test-queue-file14g.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in14g 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref14g1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file14g.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref14g2 cleanup_milter.tmp2
+ rm -f test-queue-file14g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15a: cleanup_milter test-queue-file15 cleanup_milter.in15a \
+ cleanup_milter.ref15a1 ../postcat/postcat cleanup_milter.ref15a2 \
+ cleanup_milter.reg15a
+ cp test-queue-file15 test-queue-file15a.tmp
+ chmod u+w test-queue-file15a.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15a.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15a2 cleanup_milter.tmp2
+ rm -f test-queue-file15a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15b: cleanup_milter test-queue-file15 cleanup_milter.in15b \
+ cleanup_milter.ref15b1 ../postcat/postcat cleanup_milter.ref15b2 \
+ cleanup_milter.reg15b
+ cp test-queue-file15 test-queue-file15b.tmp
+ chmod u+w test-queue-file15b.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15b.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15b2 cleanup_milter.tmp2
+ rm -f test-queue-file15b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15c: cleanup_milter test-queue-file15 cleanup_milter.in15c \
+ cleanup_milter.ref15c1 ../postcat/postcat cleanup_milter.ref15c2 \
+ cleanup_milter.reg15c
+ cp test-queue-file15 test-queue-file15c.tmp
+ chmod u+w test-queue-file15c.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15c.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15c2 cleanup_milter.tmp2
+ rm -f test-queue-file15c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15d: cleanup_milter test-queue-file15 cleanup_milter.in15d \
+ cleanup_milter.ref15d1 ../postcat/postcat cleanup_milter.ref15d2 \
+ cleanup_milter.reg15d
+ cp test-queue-file15 test-queue-file15d.tmp
+ chmod u+w test-queue-file15d.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15d.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15d2 cleanup_milter.tmp2
+ rm -f test-queue-file15d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15e: cleanup_milter test-queue-file15 cleanup_milter.in15e \
+ cleanup_milter.ref15e1 ../postcat/postcat cleanup_milter.ref15e2 \
+ cleanup_milter.reg15e
+ cp test-queue-file15 test-queue-file15e.tmp
+ chmod u+w test-queue-file15e.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15e 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15e1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15e.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15e2 cleanup_milter.tmp2
+ rm -f test-queue-file15e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15f: cleanup_milter test-queue-file15 cleanup_milter.in15f \
+ cleanup_milter.ref15f1 ../postcat/postcat cleanup_milter.ref15f2 \
+ cleanup_milter.reg15f
+ cp test-queue-file15 test-queue-file15f.tmp
+ chmod u+w test-queue-file15f.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15f 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15f1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15f.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15f2 cleanup_milter.tmp2
+ rm -f test-queue-file15f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15g: cleanup_milter test-queue-file15 cleanup_milter.in15g \
+ cleanup_milter.ref15g1 ../postcat/postcat cleanup_milter.ref15g2 \
+ cleanup_milter.reg15g
+ cp test-queue-file15 test-queue-file15g.tmp
+ chmod u+w test-queue-file15g.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15g 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15g1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15g.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15g2 cleanup_milter.tmp2
+ rm -f test-queue-file15g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15h: cleanup_milter test-queue-file15 cleanup_milter.in15h \
+ cleanup_milter.ref15h1 ../postcat/postcat cleanup_milter.ref15h2 \
+ cleanup_milter.reg15h
+ cp test-queue-file15 test-queue-file15h.tmp
+ chmod u+w test-queue-file15h.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15h 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15h1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15h.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15h2 cleanup_milter.tmp2
+ rm -f test-queue-file15h.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test15i: cleanup_milter test-queue-file15 cleanup_milter.in15i \
+ cleanup_milter.ref15i1 ../postcat/postcat cleanup_milter.ref15i2 \
+ cleanup_milter.reg15i
+ cp test-queue-file15 test-queue-file15i.tmp
+ chmod u+w test-queue-file15i.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in15i 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref15i1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file15i.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref15i2 cleanup_milter.tmp2
+ rm -f test-queue-file15i.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test16a: cleanup_milter test-queue-file16 cleanup_milter.in16a \
+ cleanup_milter.ref16a1 ../postcat/postcat cleanup_milter.ref16a2
+ cp test-queue-file16 test-queue-file16a.tmp
+ chmod u+w test-queue-file16a.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in16a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref16a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file16a.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref16a2 cleanup_milter.tmp2
+ rm -f test-queue-file16a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test16b: cleanup_milter test-queue-file16 cleanup_milter.in16b \
+ cleanup_milter.ref16b1 ../postcat/postcat cleanup_milter.ref16b2
+ cp test-queue-file16 test-queue-file16b.tmp
+ chmod u+w test-queue-file16b.tmp
+ $(SHLIB_ENV) ./cleanup_milter <cleanup_milter.in16b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref16b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) ../postcat/postcat -ov test-queue-file16b.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref16b2 cleanup_milter.tmp2
+ rm -f test-queue-file16b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+cleanup.o: ../../include/argv.h
+cleanup.o: ../../include/attr.h
+cleanup.o: ../../include/been_here.h
+cleanup.o: ../../include/check_arg.h
+cleanup.o: ../../include/cleanup_user.h
+cleanup.o: ../../include/dict.h
+cleanup.o: ../../include/dsn_mask.h
+cleanup.o: ../../include/header_body_checks.h
+cleanup.o: ../../include/header_opts.h
+cleanup.o: ../../include/htable.h
+cleanup.o: ../../include/iostuff.h
+cleanup.o: ../../include/mail_conf.h
+cleanup.o: ../../include/mail_params.h
+cleanup.o: ../../include/mail_proto.h
+cleanup.o: ../../include/mail_server.h
+cleanup.o: ../../include/mail_stream.h
+cleanup.o: ../../include/mail_version.h
+cleanup.o: ../../include/maps.h
+cleanup.o: ../../include/match_list.h
+cleanup.o: ../../include/milter.h
+cleanup.o: ../../include/mime_state.h
+cleanup.o: ../../include/msg.h
+cleanup.o: ../../include/myflock.h
+cleanup.o: ../../include/mymalloc.h
+cleanup.o: ../../include/nvtable.h
+cleanup.o: ../../include/rec_type.h
+cleanup.o: ../../include/record.h
+cleanup.o: ../../include/resolve_clnt.h
+cleanup.o: ../../include/string_list.h
+cleanup.o: ../../include/sys_defs.h
+cleanup.o: ../../include/tok822.h
+cleanup.o: ../../include/vbuf.h
+cleanup.o: ../../include/vstream.h
+cleanup.o: ../../include/vstring.h
+cleanup.o: cleanup.c
+cleanup.o: cleanup.h
+cleanup_addr.o: ../../include/argv.h
+cleanup_addr.o: ../../include/attr.h
+cleanup_addr.o: ../../include/been_here.h
+cleanup_addr.o: ../../include/canon_addr.h
+cleanup_addr.o: ../../include/check_arg.h
+cleanup_addr.o: ../../include/cleanup_user.h
+cleanup_addr.o: ../../include/dict.h
+cleanup_addr.o: ../../include/dsn_mask.h
+cleanup_addr.o: ../../include/ext_prop.h
+cleanup_addr.o: ../../include/header_body_checks.h
+cleanup_addr.o: ../../include/header_opts.h
+cleanup_addr.o: ../../include/htable.h
+cleanup_addr.o: ../../include/iostuff.h
+cleanup_addr.o: ../../include/mail_addr.h
+cleanup_addr.o: ../../include/mail_addr_find.h
+cleanup_addr.o: ../../include/mail_addr_form.h
+cleanup_addr.o: ../../include/mail_conf.h
+cleanup_addr.o: ../../include/mail_params.h
+cleanup_addr.o: ../../include/mail_proto.h
+cleanup_addr.o: ../../include/mail_stream.h
+cleanup_addr.o: ../../include/maps.h
+cleanup_addr.o: ../../include/match_list.h
+cleanup_addr.o: ../../include/milter.h
+cleanup_addr.o: ../../include/mime_state.h
+cleanup_addr.o: ../../include/msg.h
+cleanup_addr.o: ../../include/myflock.h
+cleanup_addr.o: ../../include/mymalloc.h
+cleanup_addr.o: ../../include/nvtable.h
+cleanup_addr.o: ../../include/rec_type.h
+cleanup_addr.o: ../../include/record.h
+cleanup_addr.o: ../../include/resolve_clnt.h
+cleanup_addr.o: ../../include/smtputf8.h
+cleanup_addr.o: ../../include/string_list.h
+cleanup_addr.o: ../../include/stringops.h
+cleanup_addr.o: ../../include/sys_defs.h
+cleanup_addr.o: ../../include/tok822.h
+cleanup_addr.o: ../../include/vbuf.h
+cleanup_addr.o: ../../include/vstream.h
+cleanup_addr.o: ../../include/vstring.h
+cleanup_addr.o: cleanup.h
+cleanup_addr.o: cleanup_addr.c
+cleanup_api.o: ../../include/argv.h
+cleanup_api.o: ../../include/attr.h
+cleanup_api.o: ../../include/been_here.h
+cleanup_api.o: ../../include/bounce.h
+cleanup_api.o: ../../include/check_arg.h
+cleanup_api.o: ../../include/cleanup_user.h
+cleanup_api.o: ../../include/deliver_request.h
+cleanup_api.o: ../../include/dict.h
+cleanup_api.o: ../../include/dsn.h
+cleanup_api.o: ../../include/dsn_buf.h
+cleanup_api.o: ../../include/dsn_mask.h
+cleanup_api.o: ../../include/header_body_checks.h
+cleanup_api.o: ../../include/header_opts.h
+cleanup_api.o: ../../include/htable.h
+cleanup_api.o: ../../include/iostuff.h
+cleanup_api.o: ../../include/mail_conf.h
+cleanup_api.o: ../../include/mail_flow.h
+cleanup_api.o: ../../include/mail_params.h
+cleanup_api.o: ../../include/mail_proto.h
+cleanup_api.o: ../../include/mail_queue.h
+cleanup_api.o: ../../include/mail_stream.h
+cleanup_api.o: ../../include/maps.h
+cleanup_api.o: ../../include/match_list.h
+cleanup_api.o: ../../include/milter.h
+cleanup_api.o: ../../include/mime_state.h
+cleanup_api.o: ../../include/msg.h
+cleanup_api.o: ../../include/msg_stats.h
+cleanup_api.o: ../../include/myflock.h
+cleanup_api.o: ../../include/mymalloc.h
+cleanup_api.o: ../../include/nvtable.h
+cleanup_api.o: ../../include/rec_type.h
+cleanup_api.o: ../../include/recipient_list.h
+cleanup_api.o: ../../include/resolve_clnt.h
+cleanup_api.o: ../../include/smtputf8.h
+cleanup_api.o: ../../include/string_list.h
+cleanup_api.o: ../../include/sys_defs.h
+cleanup_api.o: ../../include/tok822.h
+cleanup_api.o: ../../include/vbuf.h
+cleanup_api.o: ../../include/vstream.h
+cleanup_api.o: ../../include/vstring.h
+cleanup_api.o: cleanup.h
+cleanup_api.o: cleanup_api.c
+cleanup_body_edit.o: ../../include/argv.h
+cleanup_body_edit.o: ../../include/attr.h
+cleanup_body_edit.o: ../../include/been_here.h
+cleanup_body_edit.o: ../../include/check_arg.h
+cleanup_body_edit.o: ../../include/cleanup_user.h
+cleanup_body_edit.o: ../../include/dict.h
+cleanup_body_edit.o: ../../include/dsn_mask.h
+cleanup_body_edit.o: ../../include/header_body_checks.h
+cleanup_body_edit.o: ../../include/header_opts.h
+cleanup_body_edit.o: ../../include/htable.h
+cleanup_body_edit.o: ../../include/mail_conf.h
+cleanup_body_edit.o: ../../include/mail_stream.h
+cleanup_body_edit.o: ../../include/maps.h
+cleanup_body_edit.o: ../../include/match_list.h
+cleanup_body_edit.o: ../../include/milter.h
+cleanup_body_edit.o: ../../include/mime_state.h
+cleanup_body_edit.o: ../../include/msg.h
+cleanup_body_edit.o: ../../include/myflock.h
+cleanup_body_edit.o: ../../include/mymalloc.h
+cleanup_body_edit.o: ../../include/nvtable.h
+cleanup_body_edit.o: ../../include/rec_type.h
+cleanup_body_edit.o: ../../include/record.h
+cleanup_body_edit.o: ../../include/resolve_clnt.h
+cleanup_body_edit.o: ../../include/string_list.h
+cleanup_body_edit.o: ../../include/sys_defs.h
+cleanup_body_edit.o: ../../include/tok822.h
+cleanup_body_edit.o: ../../include/vbuf.h
+cleanup_body_edit.o: ../../include/vstream.h
+cleanup_body_edit.o: ../../include/vstring.h
+cleanup_body_edit.o: cleanup.h
+cleanup_body_edit.o: cleanup_body_edit.c
+cleanup_bounce.o: ../../include/argv.h
+cleanup_bounce.o: ../../include/attr.h
+cleanup_bounce.o: ../../include/been_here.h
+cleanup_bounce.o: ../../include/bounce.h
+cleanup_bounce.o: ../../include/check_arg.h
+cleanup_bounce.o: ../../include/cleanup_user.h
+cleanup_bounce.o: ../../include/deliver_request.h
+cleanup_bounce.o: ../../include/dict.h
+cleanup_bounce.o: ../../include/dsn.h
+cleanup_bounce.o: ../../include/dsn_buf.h
+cleanup_bounce.o: ../../include/dsn_mask.h
+cleanup_bounce.o: ../../include/dsn_util.h
+cleanup_bounce.o: ../../include/header_body_checks.h
+cleanup_bounce.o: ../../include/header_opts.h
+cleanup_bounce.o: ../../include/htable.h
+cleanup_bounce.o: ../../include/iostuff.h
+cleanup_bounce.o: ../../include/mail_conf.h
+cleanup_bounce.o: ../../include/mail_params.h
+cleanup_bounce.o: ../../include/mail_proto.h
+cleanup_bounce.o: ../../include/mail_queue.h
+cleanup_bounce.o: ../../include/mail_stream.h
+cleanup_bounce.o: ../../include/maps.h
+cleanup_bounce.o: ../../include/match_list.h
+cleanup_bounce.o: ../../include/milter.h
+cleanup_bounce.o: ../../include/mime_state.h
+cleanup_bounce.o: ../../include/msg.h
+cleanup_bounce.o: ../../include/msg_stats.h
+cleanup_bounce.o: ../../include/myflock.h
+cleanup_bounce.o: ../../include/mymalloc.h
+cleanup_bounce.o: ../../include/nvtable.h
+cleanup_bounce.o: ../../include/rec_attr_map.h
+cleanup_bounce.o: ../../include/rec_type.h
+cleanup_bounce.o: ../../include/recipient_list.h
+cleanup_bounce.o: ../../include/record.h
+cleanup_bounce.o: ../../include/resolve_clnt.h
+cleanup_bounce.o: ../../include/string_list.h
+cleanup_bounce.o: ../../include/stringops.h
+cleanup_bounce.o: ../../include/sys_defs.h
+cleanup_bounce.o: ../../include/tok822.h
+cleanup_bounce.o: ../../include/vbuf.h
+cleanup_bounce.o: ../../include/vstream.h
+cleanup_bounce.o: ../../include/vstring.h
+cleanup_bounce.o: cleanup.h
+cleanup_bounce.o: cleanup_bounce.c
+cleanup_envelope.o: ../../include/argv.h
+cleanup_envelope.o: ../../include/attr.h
+cleanup_envelope.o: ../../include/been_here.h
+cleanup_envelope.o: ../../include/check_arg.h
+cleanup_envelope.o: ../../include/cleanup_user.h
+cleanup_envelope.o: ../../include/deliver_request.h
+cleanup_envelope.o: ../../include/dict.h
+cleanup_envelope.o: ../../include/dsn.h
+cleanup_envelope.o: ../../include/dsn_mask.h
+cleanup_envelope.o: ../../include/header_body_checks.h
+cleanup_envelope.o: ../../include/header_opts.h
+cleanup_envelope.o: ../../include/htable.h
+cleanup_envelope.o: ../../include/iostuff.h
+cleanup_envelope.o: ../../include/mail_conf.h
+cleanup_envelope.o: ../../include/mail_params.h
+cleanup_envelope.o: ../../include/mail_proto.h
+cleanup_envelope.o: ../../include/mail_stream.h
+cleanup_envelope.o: ../../include/maps.h
+cleanup_envelope.o: ../../include/match_list.h
+cleanup_envelope.o: ../../include/milter.h
+cleanup_envelope.o: ../../include/mime_state.h
+cleanup_envelope.o: ../../include/msg.h
+cleanup_envelope.o: ../../include/msg_stats.h
+cleanup_envelope.o: ../../include/myflock.h
+cleanup_envelope.o: ../../include/mymalloc.h
+cleanup_envelope.o: ../../include/nvtable.h
+cleanup_envelope.o: ../../include/qmgr_user.h
+cleanup_envelope.o: ../../include/rec_attr_map.h
+cleanup_envelope.o: ../../include/rec_type.h
+cleanup_envelope.o: ../../include/recipient_list.h
+cleanup_envelope.o: ../../include/record.h
+cleanup_envelope.o: ../../include/resolve_clnt.h
+cleanup_envelope.o: ../../include/smtputf8.h
+cleanup_envelope.o: ../../include/string_list.h
+cleanup_envelope.o: ../../include/stringops.h
+cleanup_envelope.o: ../../include/sys_defs.h
+cleanup_envelope.o: ../../include/tok822.h
+cleanup_envelope.o: ../../include/vbuf.h
+cleanup_envelope.o: ../../include/verp_sender.h
+cleanup_envelope.o: ../../include/vstream.h
+cleanup_envelope.o: ../../include/vstring.h
+cleanup_envelope.o: cleanup.h
+cleanup_envelope.o: cleanup_envelope.c
+cleanup_extracted.o: ../../include/argv.h
+cleanup_extracted.o: ../../include/attr.h
+cleanup_extracted.o: ../../include/been_here.h
+cleanup_extracted.o: ../../include/check_arg.h
+cleanup_extracted.o: ../../include/cleanup_user.h
+cleanup_extracted.o: ../../include/dict.h
+cleanup_extracted.o: ../../include/dsn_mask.h
+cleanup_extracted.o: ../../include/header_body_checks.h
+cleanup_extracted.o: ../../include/header_opts.h
+cleanup_extracted.o: ../../include/htable.h
+cleanup_extracted.o: ../../include/iostuff.h
+cleanup_extracted.o: ../../include/mail_conf.h
+cleanup_extracted.o: ../../include/mail_params.h
+cleanup_extracted.o: ../../include/mail_proto.h
+cleanup_extracted.o: ../../include/mail_stream.h
+cleanup_extracted.o: ../../include/maps.h
+cleanup_extracted.o: ../../include/match_list.h
+cleanup_extracted.o: ../../include/milter.h
+cleanup_extracted.o: ../../include/mime_state.h
+cleanup_extracted.o: ../../include/msg.h
+cleanup_extracted.o: ../../include/myflock.h
+cleanup_extracted.o: ../../include/mymalloc.h
+cleanup_extracted.o: ../../include/nvtable.h
+cleanup_extracted.o: ../../include/qmgr_user.h
+cleanup_extracted.o: ../../include/rec_attr_map.h
+cleanup_extracted.o: ../../include/rec_type.h
+cleanup_extracted.o: ../../include/record.h
+cleanup_extracted.o: ../../include/resolve_clnt.h
+cleanup_extracted.o: ../../include/string_list.h
+cleanup_extracted.o: ../../include/stringops.h
+cleanup_extracted.o: ../../include/sys_defs.h
+cleanup_extracted.o: ../../include/tok822.h
+cleanup_extracted.o: ../../include/vbuf.h
+cleanup_extracted.o: ../../include/vstream.h
+cleanup_extracted.o: ../../include/vstring.h
+cleanup_extracted.o: cleanup.h
+cleanup_extracted.o: cleanup_extracted.c
+cleanup_final.o: ../../include/argv.h
+cleanup_final.o: ../../include/attr.h
+cleanup_final.o: ../../include/been_here.h
+cleanup_final.o: ../../include/check_arg.h
+cleanup_final.o: ../../include/cleanup_user.h
+cleanup_final.o: ../../include/dict.h
+cleanup_final.o: ../../include/dsn_mask.h
+cleanup_final.o: ../../include/header_body_checks.h
+cleanup_final.o: ../../include/header_opts.h
+cleanup_final.o: ../../include/htable.h
+cleanup_final.o: ../../include/mail_conf.h
+cleanup_final.o: ../../include/mail_stream.h
+cleanup_final.o: ../../include/maps.h
+cleanup_final.o: ../../include/match_list.h
+cleanup_final.o: ../../include/milter.h
+cleanup_final.o: ../../include/mime_state.h
+cleanup_final.o: ../../include/msg.h
+cleanup_final.o: ../../include/myflock.h
+cleanup_final.o: ../../include/mymalloc.h
+cleanup_final.o: ../../include/nvtable.h
+cleanup_final.o: ../../include/rec_type.h
+cleanup_final.o: ../../include/resolve_clnt.h
+cleanup_final.o: ../../include/string_list.h
+cleanup_final.o: ../../include/sys_defs.h
+cleanup_final.o: ../../include/tok822.h
+cleanup_final.o: ../../include/vbuf.h
+cleanup_final.o: ../../include/vstream.h
+cleanup_final.o: ../../include/vstring.h
+cleanup_final.o: cleanup.h
+cleanup_final.o: cleanup_final.c
+cleanup_init.o: ../../include/argv.h
+cleanup_init.o: ../../include/attr.h
+cleanup_init.o: ../../include/been_here.h
+cleanup_init.o: ../../include/check_arg.h
+cleanup_init.o: ../../include/cleanup_user.h
+cleanup_init.o: ../../include/dict.h
+cleanup_init.o: ../../include/dsn_mask.h
+cleanup_init.o: ../../include/ext_prop.h
+cleanup_init.o: ../../include/flush_clnt.h
+cleanup_init.o: ../../include/header_body_checks.h
+cleanup_init.o: ../../include/header_opts.h
+cleanup_init.o: ../../include/htable.h
+cleanup_init.o: ../../include/iostuff.h
+cleanup_init.o: ../../include/mail_addr.h
+cleanup_init.o: ../../include/mail_conf.h
+cleanup_init.o: ../../include/mail_params.h
+cleanup_init.o: ../../include/mail_stream.h
+cleanup_init.o: ../../include/mail_version.h
+cleanup_init.o: ../../include/maps.h
+cleanup_init.o: ../../include/match_list.h
+cleanup_init.o: ../../include/milter.h
+cleanup_init.o: ../../include/mime_state.h
+cleanup_init.o: ../../include/msg.h
+cleanup_init.o: ../../include/myflock.h
+cleanup_init.o: ../../include/mymalloc.h
+cleanup_init.o: ../../include/name_code.h
+cleanup_init.o: ../../include/name_mask.h
+cleanup_init.o: ../../include/nvtable.h
+cleanup_init.o: ../../include/resolve_clnt.h
+cleanup_init.o: ../../include/string_list.h
+cleanup_init.o: ../../include/stringops.h
+cleanup_init.o: ../../include/sys_defs.h
+cleanup_init.o: ../../include/tok822.h
+cleanup_init.o: ../../include/vbuf.h
+cleanup_init.o: ../../include/vstream.h
+cleanup_init.o: ../../include/vstring.h
+cleanup_init.o: cleanup.h
+cleanup_init.o: cleanup_init.c
+cleanup_map11.o: ../../include/argv.h
+cleanup_map11.o: ../../include/attr.h
+cleanup_map11.o: ../../include/been_here.h
+cleanup_map11.o: ../../include/check_arg.h
+cleanup_map11.o: ../../include/cleanup_user.h
+cleanup_map11.o: ../../include/dict.h
+cleanup_map11.o: ../../include/dsn_mask.h
+cleanup_map11.o: ../../include/header_body_checks.h
+cleanup_map11.o: ../../include/header_opts.h
+cleanup_map11.o: ../../include/htable.h
+cleanup_map11.o: ../../include/mail_addr_form.h
+cleanup_map11.o: ../../include/mail_addr_map.h
+cleanup_map11.o: ../../include/mail_conf.h
+cleanup_map11.o: ../../include/mail_stream.h
+cleanup_map11.o: ../../include/maps.h
+cleanup_map11.o: ../../include/match_list.h
+cleanup_map11.o: ../../include/milter.h
+cleanup_map11.o: ../../include/mime_state.h
+cleanup_map11.o: ../../include/msg.h
+cleanup_map11.o: ../../include/myflock.h
+cleanup_map11.o: ../../include/mymalloc.h
+cleanup_map11.o: ../../include/nvtable.h
+cleanup_map11.o: ../../include/quote_822_local.h
+cleanup_map11.o: ../../include/quote_flags.h
+cleanup_map11.o: ../../include/resolve_clnt.h
+cleanup_map11.o: ../../include/string_list.h
+cleanup_map11.o: ../../include/stringops.h
+cleanup_map11.o: ../../include/sys_defs.h
+cleanup_map11.o: ../../include/tok822.h
+cleanup_map11.o: ../../include/vbuf.h
+cleanup_map11.o: ../../include/vstream.h
+cleanup_map11.o: ../../include/vstring.h
+cleanup_map11.o: cleanup.h
+cleanup_map11.o: cleanup_map11.c
+cleanup_map1n.o: ../../include/argv.h
+cleanup_map1n.o: ../../include/attr.h
+cleanup_map1n.o: ../../include/been_here.h
+cleanup_map1n.o: ../../include/check_arg.h
+cleanup_map1n.o: ../../include/cleanup_user.h
+cleanup_map1n.o: ../../include/dict.h
+cleanup_map1n.o: ../../include/dsn_mask.h
+cleanup_map1n.o: ../../include/header_body_checks.h
+cleanup_map1n.o: ../../include/header_opts.h
+cleanup_map1n.o: ../../include/htable.h
+cleanup_map1n.o: ../../include/mail_addr_form.h
+cleanup_map1n.o: ../../include/mail_addr_map.h
+cleanup_map1n.o: ../../include/mail_conf.h
+cleanup_map1n.o: ../../include/mail_params.h
+cleanup_map1n.o: ../../include/mail_stream.h
+cleanup_map1n.o: ../../include/maps.h
+cleanup_map1n.o: ../../include/match_list.h
+cleanup_map1n.o: ../../include/milter.h
+cleanup_map1n.o: ../../include/mime_state.h
+cleanup_map1n.o: ../../include/msg.h
+cleanup_map1n.o: ../../include/myflock.h
+cleanup_map1n.o: ../../include/mymalloc.h
+cleanup_map1n.o: ../../include/nvtable.h
+cleanup_map1n.o: ../../include/quote_822_local.h
+cleanup_map1n.o: ../../include/quote_flags.h
+cleanup_map1n.o: ../../include/resolve_clnt.h
+cleanup_map1n.o: ../../include/string_list.h
+cleanup_map1n.o: ../../include/stringops.h
+cleanup_map1n.o: ../../include/sys_defs.h
+cleanup_map1n.o: ../../include/tok822.h
+cleanup_map1n.o: ../../include/vbuf.h
+cleanup_map1n.o: ../../include/vstream.h
+cleanup_map1n.o: ../../include/vstring.h
+cleanup_map1n.o: cleanup.h
+cleanup_map1n.o: cleanup_map1n.c
+cleanup_masquerade.o: ../../include/argv.h
+cleanup_masquerade.o: ../../include/attr.h
+cleanup_masquerade.o: ../../include/been_here.h
+cleanup_masquerade.o: ../../include/check_arg.h
+cleanup_masquerade.o: ../../include/cleanup_user.h
+cleanup_masquerade.o: ../../include/dict.h
+cleanup_masquerade.o: ../../include/dsn_mask.h
+cleanup_masquerade.o: ../../include/header_body_checks.h
+cleanup_masquerade.o: ../../include/header_opts.h
+cleanup_masquerade.o: ../../include/htable.h
+cleanup_masquerade.o: ../../include/mail_conf.h
+cleanup_masquerade.o: ../../include/mail_params.h
+cleanup_masquerade.o: ../../include/mail_stream.h
+cleanup_masquerade.o: ../../include/maps.h
+cleanup_masquerade.o: ../../include/match_list.h
+cleanup_masquerade.o: ../../include/milter.h
+cleanup_masquerade.o: ../../include/mime_state.h
+cleanup_masquerade.o: ../../include/msg.h
+cleanup_masquerade.o: ../../include/myflock.h
+cleanup_masquerade.o: ../../include/mymalloc.h
+cleanup_masquerade.o: ../../include/nvtable.h
+cleanup_masquerade.o: ../../include/quote_822_local.h
+cleanup_masquerade.o: ../../include/quote_flags.h
+cleanup_masquerade.o: ../../include/resolve_clnt.h
+cleanup_masquerade.o: ../../include/string_list.h
+cleanup_masquerade.o: ../../include/stringops.h
+cleanup_masquerade.o: ../../include/sys_defs.h
+cleanup_masquerade.o: ../../include/tok822.h
+cleanup_masquerade.o: ../../include/vbuf.h
+cleanup_masquerade.o: ../../include/vstream.h
+cleanup_masquerade.o: ../../include/vstring.h
+cleanup_masquerade.o: cleanup.h
+cleanup_masquerade.o: cleanup_masquerade.c
+cleanup_message.o: ../../include/argv.h
+cleanup_message.o: ../../include/attr.h
+cleanup_message.o: ../../include/been_here.h
+cleanup_message.o: ../../include/check_arg.h
+cleanup_message.o: ../../include/cleanup_user.h
+cleanup_message.o: ../../include/conv_time.h
+cleanup_message.o: ../../include/dict.h
+cleanup_message.o: ../../include/dsn_mask.h
+cleanup_message.o: ../../include/dsn_util.h
+cleanup_message.o: ../../include/ext_prop.h
+cleanup_message.o: ../../include/header_body_checks.h
+cleanup_message.o: ../../include/header_opts.h
+cleanup_message.o: ../../include/htable.h
+cleanup_message.o: ../../include/info_log_addr_form.h
+cleanup_message.o: ../../include/iostuff.h
+cleanup_message.o: ../../include/is_header.h
+cleanup_message.o: ../../include/lex_822.h
+cleanup_message.o: ../../include/mail_addr.h
+cleanup_message.o: ../../include/mail_conf.h
+cleanup_message.o: ../../include/mail_date.h
+cleanup_message.o: ../../include/mail_params.h
+cleanup_message.o: ../../include/mail_proto.h
+cleanup_message.o: ../../include/mail_stream.h
+cleanup_message.o: ../../include/maps.h
+cleanup_message.o: ../../include/match_list.h
+cleanup_message.o: ../../include/milter.h
+cleanup_message.o: ../../include/mime_state.h
+cleanup_message.o: ../../include/msg.h
+cleanup_message.o: ../../include/myflock.h
+cleanup_message.o: ../../include/mymalloc.h
+cleanup_message.o: ../../include/nvtable.h
+cleanup_message.o: ../../include/quote_822_local.h
+cleanup_message.o: ../../include/quote_flags.h
+cleanup_message.o: ../../include/rec_type.h
+cleanup_message.o: ../../include/record.h
+cleanup_message.o: ../../include/resolve_clnt.h
+cleanup_message.o: ../../include/split_at.h
+cleanup_message.o: ../../include/string_list.h
+cleanup_message.o: ../../include/stringops.h
+cleanup_message.o: ../../include/sys_defs.h
+cleanup_message.o: ../../include/tok822.h
+cleanup_message.o: ../../include/vbuf.h
+cleanup_message.o: ../../include/vstream.h
+cleanup_message.o: ../../include/vstring.h
+cleanup_message.o: cleanup.h
+cleanup_message.o: cleanup_message.c
+cleanup_milter.o: ../../include/argv.h
+cleanup_milter.o: ../../include/attr.h
+cleanup_milter.o: ../../include/been_here.h
+cleanup_milter.o: ../../include/check_arg.h
+cleanup_milter.o: ../../include/cleanup_user.h
+cleanup_milter.o: ../../include/dict.h
+cleanup_milter.o: ../../include/dsn_mask.h
+cleanup_milter.o: ../../include/dsn_util.h
+cleanup_milter.o: ../../include/header_body_checks.h
+cleanup_milter.o: ../../include/header_opts.h
+cleanup_milter.o: ../../include/htable.h
+cleanup_milter.o: ../../include/inet_proto.h
+cleanup_milter.o: ../../include/info_log_addr_form.h
+cleanup_milter.o: ../../include/iostuff.h
+cleanup_milter.o: ../../include/is_header.h
+cleanup_milter.o: ../../include/lex_822.h
+cleanup_milter.o: ../../include/mail_conf.h
+cleanup_milter.o: ../../include/mail_params.h
+cleanup_milter.o: ../../include/mail_proto.h
+cleanup_milter.o: ../../include/mail_stream.h
+cleanup_milter.o: ../../include/maps.h
+cleanup_milter.o: ../../include/match_list.h
+cleanup_milter.o: ../../include/milter.h
+cleanup_milter.o: ../../include/mime_state.h
+cleanup_milter.o: ../../include/msg.h
+cleanup_milter.o: ../../include/myflock.h
+cleanup_milter.o: ../../include/mymalloc.h
+cleanup_milter.o: ../../include/nvtable.h
+cleanup_milter.o: ../../include/off_cvt.h
+cleanup_milter.o: ../../include/quote_821_local.h
+cleanup_milter.o: ../../include/quote_flags.h
+cleanup_milter.o: ../../include/rec_attr_map.h
+cleanup_milter.o: ../../include/rec_type.h
+cleanup_milter.o: ../../include/record.h
+cleanup_milter.o: ../../include/resolve_clnt.h
+cleanup_milter.o: ../../include/string_list.h
+cleanup_milter.o: ../../include/stringops.h
+cleanup_milter.o: ../../include/sys_defs.h
+cleanup_milter.o: ../../include/tok822.h
+cleanup_milter.o: ../../include/vbuf.h
+cleanup_milter.o: ../../include/vstream.h
+cleanup_milter.o: ../../include/vstring.h
+cleanup_milter.o: ../../include/xtext.h
+cleanup_milter.o: cleanup.h
+cleanup_milter.o: cleanup_milter.c
+cleanup_out.o: ../../include/argv.h
+cleanup_out.o: ../../include/attr.h
+cleanup_out.o: ../../include/been_here.h
+cleanup_out.o: ../../include/check_arg.h
+cleanup_out.o: ../../include/cleanup_user.h
+cleanup_out.o: ../../include/dict.h
+cleanup_out.o: ../../include/dsn_mask.h
+cleanup_out.o: ../../include/header_body_checks.h
+cleanup_out.o: ../../include/header_opts.h
+cleanup_out.o: ../../include/htable.h
+cleanup_out.o: ../../include/lex_822.h
+cleanup_out.o: ../../include/mail_conf.h
+cleanup_out.o: ../../include/mail_params.h
+cleanup_out.o: ../../include/mail_stream.h
+cleanup_out.o: ../../include/maps.h
+cleanup_out.o: ../../include/match_list.h
+cleanup_out.o: ../../include/milter.h
+cleanup_out.o: ../../include/mime_state.h
+cleanup_out.o: ../../include/msg.h
+cleanup_out.o: ../../include/myflock.h
+cleanup_out.o: ../../include/mymalloc.h
+cleanup_out.o: ../../include/nvtable.h
+cleanup_out.o: ../../include/rec_type.h
+cleanup_out.o: ../../include/record.h
+cleanup_out.o: ../../include/resolve_clnt.h
+cleanup_out.o: ../../include/smtputf8.h
+cleanup_out.o: ../../include/split_at.h
+cleanup_out.o: ../../include/string_list.h
+cleanup_out.o: ../../include/stringops.h
+cleanup_out.o: ../../include/sys_defs.h
+cleanup_out.o: ../../include/tok822.h
+cleanup_out.o: ../../include/vbuf.h
+cleanup_out.o: ../../include/vstream.h
+cleanup_out.o: ../../include/vstring.h
+cleanup_out.o: cleanup.h
+cleanup_out.o: cleanup_out.c
+cleanup_out_recipient.o: ../../include/argv.h
+cleanup_out_recipient.o: ../../include/attr.h
+cleanup_out_recipient.o: ../../include/been_here.h
+cleanup_out_recipient.o: ../../include/bounce.h
+cleanup_out_recipient.o: ../../include/check_arg.h
+cleanup_out_recipient.o: ../../include/cleanup_user.h
+cleanup_out_recipient.o: ../../include/deliver_request.h
+cleanup_out_recipient.o: ../../include/dict.h
+cleanup_out_recipient.o: ../../include/dsn.h
+cleanup_out_recipient.o: ../../include/dsn_buf.h
+cleanup_out_recipient.o: ../../include/dsn_mask.h
+cleanup_out_recipient.o: ../../include/ext_prop.h
+cleanup_out_recipient.o: ../../include/header_body_checks.h
+cleanup_out_recipient.o: ../../include/header_opts.h
+cleanup_out_recipient.o: ../../include/htable.h
+cleanup_out_recipient.o: ../../include/iostuff.h
+cleanup_out_recipient.o: ../../include/mail_conf.h
+cleanup_out_recipient.o: ../../include/mail_params.h
+cleanup_out_recipient.o: ../../include/mail_proto.h
+cleanup_out_recipient.o: ../../include/mail_queue.h
+cleanup_out_recipient.o: ../../include/mail_stream.h
+cleanup_out_recipient.o: ../../include/maps.h
+cleanup_out_recipient.o: ../../include/match_list.h
+cleanup_out_recipient.o: ../../include/milter.h
+cleanup_out_recipient.o: ../../include/mime_state.h
+cleanup_out_recipient.o: ../../include/msg.h
+cleanup_out_recipient.o: ../../include/msg_stats.h
+cleanup_out_recipient.o: ../../include/myflock.h
+cleanup_out_recipient.o: ../../include/mymalloc.h
+cleanup_out_recipient.o: ../../include/nvtable.h
+cleanup_out_recipient.o: ../../include/rec_type.h
+cleanup_out_recipient.o: ../../include/recipient_list.h
+cleanup_out_recipient.o: ../../include/resolve_clnt.h
+cleanup_out_recipient.o: ../../include/string_list.h
+cleanup_out_recipient.o: ../../include/sys_defs.h
+cleanup_out_recipient.o: ../../include/tok822.h
+cleanup_out_recipient.o: ../../include/trace.h
+cleanup_out_recipient.o: ../../include/vbuf.h
+cleanup_out_recipient.o: ../../include/verify.h
+cleanup_out_recipient.o: ../../include/vstream.h
+cleanup_out_recipient.o: ../../include/vstring.h
+cleanup_out_recipient.o: cleanup.h
+cleanup_out_recipient.o: cleanup_out_recipient.c
+cleanup_region.o: ../../include/argv.h
+cleanup_region.o: ../../include/attr.h
+cleanup_region.o: ../../include/been_here.h
+cleanup_region.o: ../../include/check_arg.h
+cleanup_region.o: ../../include/cleanup_user.h
+cleanup_region.o: ../../include/dict.h
+cleanup_region.o: ../../include/dsn_mask.h
+cleanup_region.o: ../../include/header_body_checks.h
+cleanup_region.o: ../../include/header_opts.h
+cleanup_region.o: ../../include/htable.h
+cleanup_region.o: ../../include/mail_conf.h
+cleanup_region.o: ../../include/mail_stream.h
+cleanup_region.o: ../../include/maps.h
+cleanup_region.o: ../../include/match_list.h
+cleanup_region.o: ../../include/milter.h
+cleanup_region.o: ../../include/mime_state.h
+cleanup_region.o: ../../include/msg.h
+cleanup_region.o: ../../include/myflock.h
+cleanup_region.o: ../../include/mymalloc.h
+cleanup_region.o: ../../include/nvtable.h
+cleanup_region.o: ../../include/resolve_clnt.h
+cleanup_region.o: ../../include/string_list.h
+cleanup_region.o: ../../include/sys_defs.h
+cleanup_region.o: ../../include/tok822.h
+cleanup_region.o: ../../include/vbuf.h
+cleanup_region.o: ../../include/vstream.h
+cleanup_region.o: ../../include/vstring.h
+cleanup_region.o: ../../include/warn_stat.h
+cleanup_region.o: cleanup.h
+cleanup_region.o: cleanup_region.c
+cleanup_rewrite.o: ../../include/argv.h
+cleanup_rewrite.o: ../../include/attr.h
+cleanup_rewrite.o: ../../include/been_here.h
+cleanup_rewrite.o: ../../include/check_arg.h
+cleanup_rewrite.o: ../../include/cleanup_user.h
+cleanup_rewrite.o: ../../include/dict.h
+cleanup_rewrite.o: ../../include/dsn_mask.h
+cleanup_rewrite.o: ../../include/header_body_checks.h
+cleanup_rewrite.o: ../../include/header_opts.h
+cleanup_rewrite.o: ../../include/htable.h
+cleanup_rewrite.o: ../../include/iostuff.h
+cleanup_rewrite.o: ../../include/mail_conf.h
+cleanup_rewrite.o: ../../include/mail_proto.h
+cleanup_rewrite.o: ../../include/mail_stream.h
+cleanup_rewrite.o: ../../include/maps.h
+cleanup_rewrite.o: ../../include/match_list.h
+cleanup_rewrite.o: ../../include/milter.h
+cleanup_rewrite.o: ../../include/mime_state.h
+cleanup_rewrite.o: ../../include/msg.h
+cleanup_rewrite.o: ../../include/myflock.h
+cleanup_rewrite.o: ../../include/mymalloc.h
+cleanup_rewrite.o: ../../include/nvtable.h
+cleanup_rewrite.o: ../../include/quote_822_local.h
+cleanup_rewrite.o: ../../include/quote_flags.h
+cleanup_rewrite.o: ../../include/resolve_clnt.h
+cleanup_rewrite.o: ../../include/rewrite_clnt.h
+cleanup_rewrite.o: ../../include/string_list.h
+cleanup_rewrite.o: ../../include/sys_defs.h
+cleanup_rewrite.o: ../../include/tok822.h
+cleanup_rewrite.o: ../../include/vbuf.h
+cleanup_rewrite.o: ../../include/vstream.h
+cleanup_rewrite.o: ../../include/vstring.h
+cleanup_rewrite.o: cleanup.h
+cleanup_rewrite.o: cleanup_rewrite.c
+cleanup_state.o: ../../include/argv.h
+cleanup_state.o: ../../include/attr.h
+cleanup_state.o: ../../include/been_here.h
+cleanup_state.o: ../../include/check_arg.h
+cleanup_state.o: ../../include/cleanup_user.h
+cleanup_state.o: ../../include/dict.h
+cleanup_state.o: ../../include/dsn_mask.h
+cleanup_state.o: ../../include/header_body_checks.h
+cleanup_state.o: ../../include/header_opts.h
+cleanup_state.o: ../../include/htable.h
+cleanup_state.o: ../../include/iostuff.h
+cleanup_state.o: ../../include/mail_conf.h
+cleanup_state.o: ../../include/mail_params.h
+cleanup_state.o: ../../include/mail_proto.h
+cleanup_state.o: ../../include/mail_stream.h
+cleanup_state.o: ../../include/maps.h
+cleanup_state.o: ../../include/match_list.h
+cleanup_state.o: ../../include/milter.h
+cleanup_state.o: ../../include/mime_state.h
+cleanup_state.o: ../../include/myflock.h
+cleanup_state.o: ../../include/mymalloc.h
+cleanup_state.o: ../../include/nvtable.h
+cleanup_state.o: ../../include/resolve_clnt.h
+cleanup_state.o: ../../include/string_list.h
+cleanup_state.o: ../../include/sys_defs.h
+cleanup_state.o: ../../include/tok822.h
+cleanup_state.o: ../../include/vbuf.h
+cleanup_state.o: ../../include/vstream.h
+cleanup_state.o: ../../include/vstring.h
+cleanup_state.o: cleanup.h
+cleanup_state.o: cleanup_state.c
diff --git a/src/cleanup/bug1.file b/src/cleanup/bug1.file
new file mode 100644
index 0000000..8412ae3
--- /dev/null
+++ b/src/cleanup/bug1.file
Binary files differ
diff --git a/src/cleanup/bug1.file.ref b/src/cleanup/bug1.file.ref
new file mode 100755
index 0000000..229d26d
--- /dev/null
+++ b/src/cleanup/bug1.file.ref
Binary files differ
diff --git a/src/cleanup/bug1.in b/src/cleanup/bug1.in
new file mode 100644
index 0000000..bda18cf
--- /dev/null
+++ b/src/cleanup/bug1.in
@@ -0,0 +1,41 @@
+#verbose on
+open bug1.file.tmp
+
+# Symptom:
+#
+# infinite loop in postcat and in delivery agents
+#
+# Cause:
+#
+# Failure to update location info after following a pointer record,
+# while updating a message header record
+#
+# Analysis:
+#
+# This happens with repeated updates of the same message header.
+# After the first update, the update #1 header record sits in the
+# heap at the end of the queue file, and is followed by a reverse
+# pointer to the start of the next message header record or the
+# message body, somewhere in the middle of the queue file.
+#
+# The problem started with update #2 of that same message header.
+# While following the reverse pointer record after the update #1
+# header record to find out the start of the next header or message
+# body, the header updating routine did not update its notion of
+# where it was. Thus, it believed that the next header or body record
+# was located after the reverse pointer record. That was not the
+# middle of the message, but the end of the queue file. The second
+# update would result in an update #2 header record, followed by a
+# reverse pointer to what used to be the end of the queue file, but
+# had meanwhile become the location of the update #2 header record.
+#
+# Thus, anything that tried to deliver mail would loop on the update
+# #2 header record. After update update #3 of the same header, the
+# delivery agent would loop on the update #3 record, etc.
+
+upd_header 1 Subject long header text
+upd_header 1 Subject long header text
+upd_header 1 Subject long header text
+upd_header 1 Subject long header text
+
+close
diff --git a/src/cleanup/bug1.ref b/src/cleanup/bug1.ref
new file mode 100644
index 0000000..362d2cb
--- /dev/null
+++ b/src/cleanup/bug1.ref
@@ -0,0 +1,56 @@
+*** ENVELOPE RECORDS bug1.file.tmp ***
+ 0 message_size: 441 813 3 0 441
+ 81 message_arrival_time: Sat Jan 20 19:52:41 2007
+ 100 create_time: Sat Jan 20 19:52:47 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender: wietse@porcupine.org
+ 169 named_attribute: log_client_name=hades.porcupine.org
+ 206 named_attribute: log_client_address=168.100.189.10
+ 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10]
+ 297 named_attribute: log_helo_name=hades.porcupine.org
+ 332 named_attribute: log_protocol_name=SMTP
+ 356 named_attribute: client_name=hades.porcupine.org
+ 389 named_attribute: reverse_client_name=hades.porcupine.org
+ 430 named_attribute: client_address=168.100.189.10
+ 461 named_attribute: helo_name=hades.porcupine.org
+ 492 named_attribute: client_address_type=2
+ 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org
+ 558 original_recipient: wietse@porcupine.org
+ 580 recipient: wietse@porcupine.org
+ 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 650 original_recipient: alias@hades.porcupine.org
+ 677 recipient: wietse@porcupine.org
+ 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 747 original_recipient: alias@hades.porcupine.org
+ 774 recipient: root@porcupine.org
+ 794 pointer_record: 0
+ 811 *** MESSAGE CONTENTS bug1.file.tmp ***
+ 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10])
+ 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405;
+ 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 989 regular_text: X: 1
+ 995 padding: 0
+ 1006 regular_text: 2
+ 1010 regular_text: 3
+ 1014 regular_text: 4
+ 1018 regular_text: 5
+ 1022 regular_text: 6
+ 1026 regular_text: 7
+ 1030 regular_text: Y: 1234567
+ 1042 padding: 0
+ 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org>
+ 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 1154 regular_text: From: wietse@porcupine.org
+ 1182 regular_text: To: undisclosed-recipients:;
+ 1212 pointer_record: 1258
+ 1258 pointer_record: 1302
+ 1302 pointer_record: 1346
+ 1346 pointer_record: 1390
+ 1390 regular_text: Subject: long header text
+ 1417 pointer_record: 1285
+ 1285 pointer_record: 1229
+ 1229 regular_text:
+ 1231 regular_text: text
+ 1237 pointer_record: 0
+ 1254 *** HEADER EXTRACTED bug1.file.tmp ***
+ 1256 *** MESSAGE FILE END bug1.file.tmp ***
diff --git a/src/cleanup/bug1.text.ref b/src/cleanup/bug1.text.ref
new file mode 100644
index 0000000..72fe3df
--- /dev/null
+++ b/src/cleanup/bug1.text.ref
@@ -0,0 +1,46 @@
+*** ENVELOPE RECORDS bug1.file.tmp ***
+message_size: 441 813 3 0 441
+message_arrival_time: Sat Jan 20 19:52:41 2007
+create_time: Sat Jan 20 19:52:47 2007
+named_attribute: rewrite_context=local
+sender: wietse@porcupine.org
+named_attribute: log_client_name=hades.porcupine.org
+named_attribute: log_client_address=168.100.189.10
+named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10]
+named_attribute: log_helo_name=hades.porcupine.org
+named_attribute: log_protocol_name=SMTP
+named_attribute: client_name=hades.porcupine.org
+named_attribute: reverse_client_name=hades.porcupine.org
+named_attribute: client_address=168.100.189.10
+named_attribute: helo_name=hades.porcupine.org
+named_attribute: client_address_type=2
+named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org
+original_recipient: wietse@porcupine.org
+recipient: wietse@porcupine.org
+named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+original_recipient: alias@hades.porcupine.org
+recipient: wietse@porcupine.org
+named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+original_recipient: alias@hades.porcupine.org
+recipient: root@porcupine.org
+*** MESSAGE CONTENTS bug1.file.tmp ***
+Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10])
+ by hades.porcupine.org (Postfix) with SMTP id 38132290405;
+ Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+X: 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+Y: 1234567
+Message-Id: <20070121005247.38132290405@hades.porcupine.org>
+Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+From: wietse@porcupine.org
+To: undisclosed-recipients:;
+Subject: long header text
+
+text
+*** HEADER EXTRACTED bug1.file.tmp ***
+*** MESSAGE FILE END bug1.file.tmp ***
diff --git a/src/cleanup/bug2.file b/src/cleanup/bug2.file
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/bug2.file
Binary files differ
diff --git a/src/cleanup/bug2.in b/src/cleanup/bug2.in
new file mode 100644
index 0000000..138ca3d
--- /dev/null
+++ b/src/cleanup/bug2.in
@@ -0,0 +1,37 @@
+#verbose on
+open bug2.file.tmp
+
+# Two bugs while updating a short Subject: header immediately before
+# a still virgin "append header" pointer record.
+#
+# Symptom:
+#
+# warning: <filename>: malformed pointer record value: <garbage>
+#
+# Cause:
+#
+# Failure to recognize the "append header" record while updating
+# a short message header
+#
+# Analysis:
+#
+# This happened while updating a header record that was followed by
+# the current "append header" record. The pointer could be the initial
+# "append header" record between message header and body, or it could
+# be a later version of that pointer somewhere in the heap.
+#
+# - Postfix considered the pointer record as any pointer record after
+# a header record. Thus, it decided that some portion of the pointer
+# record could be overwritten with the location of the new Subject:
+# header on the heap. Later "append header" operations would then
+# update old "append header" record and thus clobber part of the
+# pointer to the new Subject: header value.
+#
+# - While saving the "append header" pointer record value on the
+# heap, Postfix did not replace the still virgin "0" append header"
+# pointer record value by the actual location of the message body
+# content.
+
+upd_header 1 Subject hey!
+add_header foo foobar
+close
diff --git a/src/cleanup/bug2.ref b/src/cleanup/bug2.ref
new file mode 100644
index 0000000..6a0aab4
--- /dev/null
+++ b/src/cleanup/bug2.ref
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS bug2.file.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS bug2.file.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 pointer_record: 573
+ 573 regular_text: Subject: hey!
+ 588 padding: 0
+ 591 pointer_record: 489
+ 489 pointer_record: 608
+ 608 regular_text: foo: foobar
+ 621 padding: 0
+ 625 pointer_record: 506
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED bug2.file.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END bug2.file.tmp ***
diff --git a/src/cleanup/bug2.text.ref b/src/cleanup/bug2.text.ref
new file mode 100644
index 0000000..fd5cfe1
--- /dev/null
+++ b/src/cleanup/bug2.text.ref
@@ -0,0 +1,22 @@
+*** ENVELOPE RECORDS bug2.file.tmp ***
+message_size: 332 199 1 0 332
+message_arrival_time: Sat Jan 20 20:53:54 2007
+create_time: Sat Jan 20 20:53:59 2007
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+*** MESSAGE CONTENTS bug2.file.tmp ***
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+From: me@porcupine.org
+To: you@porcupine.org
+Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+Subject: hey!
+foo: foobar
+
+text
+*** HEADER EXTRACTED bug2.file.tmp ***
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE FILE END bug2.file.tmp ***
diff --git a/src/cleanup/bug3.file b/src/cleanup/bug3.file
new file mode 100644
index 0000000..fd9236c
--- /dev/null
+++ b/src/cleanup/bug3.file
Binary files differ
diff --git a/src/cleanup/bug3.in b/src/cleanup/bug3.in
new file mode 100644
index 0000000..1920eaf
--- /dev/null
+++ b/src/cleanup/bug3.in
@@ -0,0 +1,29 @@
+#verbose on
+open bug3.file.tmp
+
+# This was a problem with a length check in the wrong place, causing
+# a short header name to match a longer one. After successful
+# substring match, the "change header" code checked the length of
+# the header name that was found, instead of the header name that
+# was wanted.
+
+#add_header X-SpamTest-Envelope-From wietse@porcupine.org
+#upd_header 1 X-SpamTest-Envelope-From wietse@porcupine.org
+#add_header X-SpamTest-Group-ID 00000000
+#upd_header 1 X-SpamTest-Group-ID 00000000
+#add_header X-SpamTest-Info Profiles 29362 [Feb 02 2012]
+#upd_header 1 X-SpamTest-Info Profiles 29362 [Feb 02 2012]
+#add_header X-SpamTest-Method none
+#upd_header 1 X-SpamTest-Method none
+#add_header X-SpamTest-Rate 0
+#upd_header 1 X-SpamTest-Rate 0
+#add_header X-SpamTest-SPF none
+#upd_header 1 X-SpamTest-SPF none
+add_header X-SpamTest-Status Not detected
+#upd_header 1 X-SpamTest-Status Not detected
+add_header X-SpamTest-Status-Extended not_detected
+upd_header 1 X-SpamTest-Status-Extended not_detected
+#add_header X-SpamTest-Version SMTP-Filter Version 3.0.0 [0284], KAS30/Release
+#upd_header 1 X-SpamTest-Version SMTP-Filter Version 3.0.0 [0284], KAS30/Release
+
+close
diff --git a/src/cleanup/bug3.ref b/src/cleanup/bug3.ref
new file mode 100644
index 0000000..da4d162
--- /dev/null
+++ b/src/cleanup/bug3.ref
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS bug3.file.tmp ***
+ 0 message_size: 307 237 1 0 307
+ 81 message_arrival_time: Thu Feb 2 09:02:07 2012
+ 100 create_time: Thu Feb 2 09:02:07 2012
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 original_recipient: you@porcupine.org
+ 199 recipient: you@porcupine.org
+ 218 pointer_record: 0
+ 235 *** MESSAGE CONTENTS bug3.file.tmp ***
+ 237 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 299 regular_text: id 9210192461E; Thu, 2 Feb 2012 09:02:07 -0500 (EST)
+ 355 regular_text: Message-Id: <20120202140207.9210192461E@hades.porcupine.org>
+ 417 regular_text: Date: Thu, 2 Feb 2012 09:02:07 -0500 (EST)
+ 462 regular_text: From: me@porcupine.org (Wietse Venema)
+ 502 pointer_record: 565
+ 565 regular_text: X-SpamTest-Status: Not detected
+ 598 pointer_record: 615
+ 615 pointer_record: 674
+ 674 regular_text: X-SpamTest-Status-Extended: not_detected
+ 716 pointer_record: 657
+ 657 pointer_record: 519
+ 519 regular_text:
+ 521 regular_text: test
+ 527 pointer_record: 0
+ 544 *** HEADER EXTRACTED bug3.file.tmp ***
+ 546 pointer_record: 0
+ 563 *** MESSAGE FILE END bug3.file.tmp ***
diff --git a/src/cleanup/bug3.text.ref b/src/cleanup/bug3.text.ref
new file mode 100644
index 0000000..9e672b8
--- /dev/null
+++ b/src/cleanup/bug3.text.ref
@@ -0,0 +1,21 @@
+*** ENVELOPE RECORDS bug3.file.tmp ***
+message_size: 307 237 1 0 307
+message_arrival_time: Thu Feb 2 09:02:07 2012
+create_time: Thu Feb 2 09:02:07 2012
+named_attribute: rewrite_context=local
+sender_fullname: Wietse Venema
+sender: me@porcupine.org
+original_recipient: you@porcupine.org
+recipient: you@porcupine.org
+*** MESSAGE CONTENTS bug3.file.tmp ***
+Received: by hades.porcupine.org (Postfix, from userid 1001)
+ id 9210192461E; Thu, 2 Feb 2012 09:02:07 -0500 (EST)
+Message-Id: <20120202140207.9210192461E@hades.porcupine.org>
+Date: Thu, 2 Feb 2012 09:02:07 -0500 (EST)
+From: me@porcupine.org (Wietse Venema)
+X-SpamTest-Status: Not detected
+X-SpamTest-Status-Extended: not_detected
+
+test
+*** HEADER EXTRACTED bug3.file.tmp ***
+*** MESSAGE FILE END bug3.file.tmp ***
diff --git a/src/cleanup/cleanup.c b/src/cleanup/cleanup.c
new file mode 100644
index 0000000..d076e1f
--- /dev/null
+++ b/src/cleanup/cleanup.c
@@ -0,0 +1,635 @@
+/*++
+/* NAME
+/* cleanup 8
+/* SUMMARY
+/* canonicalize and enqueue Postfix message
+/* SYNOPSIS
+/* \fBcleanup\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBcleanup\fR(8) daemon processes inbound mail, inserts it
+/* into the \fBincoming\fR mail queue, and informs the queue
+/* manager of its arrival.
+/*
+/* The \fBcleanup\fR(8) daemon always performs the following transformations:
+/* .IP \(bu
+/* Insert missing message headers: (\fBResent-\fR) \fBFrom:\fR,
+/* \fBTo:\fR, \fBMessage-Id:\fR, and \fBDate:\fR.
+/* .IP \(bu
+/* Transform envelope and header addresses to the standard
+/* \fIuser@fully-qualified-domain\fR form that is expected by other
+/* Postfix programs.
+/* This task is delegated to the \fBtrivial-rewrite\fR(8) daemon.
+/* .IP \(bu
+/* Eliminate duplicate envelope recipient addresses.
+/* .IP \(bu
+/* Remove message headers: \fBBcc\fR, \fBContent-Length\fR,
+/* \fBResent-Bcc\fR, \fBReturn-Path\fR.
+/* .PP
+/* The following address transformations are optional:
+/* .IP \(bu
+/* Optionally, rewrite all envelope and header addresses according
+/* to the mappings specified in the \fBcanonical\fR(5) lookup tables.
+/* .IP \(bu
+/* Optionally, masquerade envelope sender addresses and message
+/* header addresses (i.e. strip host or domain information below
+/* all domains listed in the \fBmasquerade_domains\fR parameter,
+/* except for user names listed in \fBmasquerade_exceptions\fR).
+/* By default, address masquerading does not affect envelope recipients.
+/* .IP \(bu
+/* Optionally, expand envelope recipients according to information
+/* found in the \fBvirtual\fR(5) lookup tables.
+/* .PP
+/* The \fBcleanup\fR(8) daemon performs sanity checks on the content of
+/* each message. When it finds a problem, by default it returns a
+/* diagnostic status to the client, and leaves it up to the client
+/* to deal with the problem. Alternatively, the client can request
+/* the \fBcleanup\fR(8) daemon to bounce the message back to the sender
+/* in case of trouble.
+/* STANDARDS
+/* RFC 822 (ARPA Internet Text Messages)
+/* RFC 2045 (MIME: Format of Internet Message Bodies)
+/* RFC 2046 (MIME: Media Types)
+/* RFC 2822 (Internet Message Format)
+/* RFC 3463 (Enhanced Status Codes)
+/* RFC 3464 (Delivery status notifications)
+/* RFC 5322 (Internet Message Format)
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* Table-driven rewriting rules make it hard to express \fBif then
+/* else\fR and other logical relationships.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as
+/* \fBcleanup\fR(8)
+/* processes run for only a limited amount of time. Use the command
+/* "\fBpostfix reload\fR" to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBundisclosed_recipients_header (see 'postconf -d' output)\fR"
+/* Message header that the Postfix \fBcleanup\fR(8) server inserts when a
+/* message contains no To: or Cc: message header.
+/* .PP
+/* Available in Postfix version 2.1 only:
+/* .IP "\fBenable_errors_to (no)\fR"
+/* Report mail delivery errors to the address specified with the
+/* non-standard Errors-To: message header, instead of the envelope
+/* sender address (this feature is removed with Postfix version 2.2, is
+/* turned off by default with Postfix version 2.1, and is always turned on
+/* with older Postfix versions).
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBalways_add_missing_headers (no)\fR"
+/* Always add (Resent-) From:, To:, Date: or Message-ID: headers
+/* when not present.
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBenable_long_queue_ids (no)\fR"
+/* Enable long, non-repeating, queue IDs (queue file names).
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBmessage_drop_headers (bcc, content-length, resent-bcc, return-path)\fR"
+/* Names of message headers that the \fBcleanup\fR(8) daemon will remove
+/* after applying \fBheader_checks\fR(5) and before invoking Milter applications.
+/* BUILT-IN CONTENT FILTERING CONTROLS
+/* .ad
+/* .fi
+/* Postfix built-in content filtering is meant to stop a flood of
+/* worms or viruses. It is not a general content filter.
+/* .IP "\fBbody_checks (empty)\fR"
+/* Optional lookup tables for content inspection as specified in
+/* the \fBbody_checks\fR(5) manual page.
+/* .IP "\fBheader_checks (empty)\fR"
+/* Optional lookup tables for content inspection of primary non-MIME
+/* message headers, as specified in the \fBheader_checks\fR(5) manual page.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBbody_checks_size_limit (51200)\fR"
+/* How much text in a message body segment (or attachment, if you
+/* prefer to use that term) is subjected to body_checks inspection.
+/* .IP "\fBmime_header_checks ($header_checks)\fR"
+/* Optional lookup tables for content inspection of MIME related
+/* message headers, as described in the \fBheader_checks\fR(5) manual page.
+/* .IP "\fBnested_header_checks ($header_checks)\fR"
+/* Optional lookup tables for content inspection of non-MIME message
+/* headers in attached messages, as described in the \fBheader_checks\fR(5)
+/* manual page.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBmessage_reject_characters (empty)\fR"
+/* The set of characters that Postfix will reject in message
+/* content.
+/* .IP "\fBmessage_strip_characters (empty)\fR"
+/* The set of characters that Postfix will remove from message
+/* content.
+/* .PP
+/* Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14,
+/* 3.5.24, and later:
+/* .IP "\fBcleanup_replace_stray_cr_lf (yes)\fR"
+/* Replace each stray <CR> or <LF> character in message
+/* content with a space character, to prevent outbound SMTP smuggling,
+/* and to make the evaluation of Postfix-added DKIM or other signatures
+/* independent from how a remote mail server handles such characters.
+/* BEFORE QUEUE MILTER CONTROLS
+/* .ad
+/* .fi
+/* As of version 2.3, Postfix supports the Sendmail version 8
+/* Milter (mail filter) protocol. When mail is not received via
+/* the smtpd(8) server, the cleanup(8) server will simulate
+/* SMTP events to the extent that this is possible. For details
+/* see the MILTER_README document.
+/* .IP "\fBnon_smtpd_milters (empty)\fR"
+/* A list of Milter (mail filter) applications for new mail that
+/* does not arrive via the Postfix \fBsmtpd\fR(8) server.
+/* .IP "\fBmilter_protocol (6)\fR"
+/* The mail filter protocol version and optional protocol extensions
+/* for communication with a Milter application; prior to Postfix 2.6
+/* the default protocol is 2.
+/* .IP "\fBmilter_default_action (tempfail)\fR"
+/* The default action when a Milter (mail filter) application is
+/* unavailable or mis-configured.
+/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR"
+/* The {daemon_name} macro value for Milter (mail filter) applications.
+/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR"
+/* The {v} macro value for Milter (mail filter) applications.
+/* .IP "\fBmilter_connect_timeout (30s)\fR"
+/* The time limit for connecting to a Milter (mail filter)
+/* application, and for negotiating protocol options.
+/* .IP "\fBmilter_command_timeout (30s)\fR"
+/* The time limit for sending an SMTP command to a Milter (mail
+/* filter) application, and for receiving the response.
+/* .IP "\fBmilter_content_timeout (300s)\fR"
+/* The time limit for sending message content to a Milter (mail
+/* filter) application, and for receiving the response.
+/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after completion of an SMTP connection.
+/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP HELO or EHLO command.
+/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP MAIL FROM command.
+/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the SMTP RCPT TO command.
+/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to version 4 or higher Milter (mail
+/* filter) applications after the SMTP DATA command.
+/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to version 3 or higher Milter (mail
+/* filter) applications after an unknown SMTP command.
+/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the message end-of-data.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR"
+/* The macros that are sent to Milter (mail filter) applications
+/* after the end of the message header.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBmilter_header_checks (empty)\fR"
+/* Optional lookup tables for content inspection of message headers
+/* that are produced by Milter applications.
+/* .PP
+/* Available in Postfix version 3.1 and later:
+/* .IP "\fBmilter_macro_defaults (empty)\fR"
+/* Optional list of \fIname=value\fR pairs that specify default
+/* values for arbitrary macros that Postfix may send to Milter
+/* applications.
+/* MIME PROCESSING CONTROLS
+/* .ad
+/* .fi
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBdisable_mime_input_processing (no)\fR"
+/* Turn off MIME processing while receiving mail.
+/* .IP "\fBmime_boundary_length_limit (2048)\fR"
+/* The maximal length of MIME multipart boundary strings.
+/* .IP "\fBmime_nesting_limit (100)\fR"
+/* The maximal recursion level that the MIME processor will handle.
+/* .IP "\fBstrict_8bitmime (no)\fR"
+/* Enable both strict_7bit_headers and strict_8bitmime_body.
+/* .IP "\fBstrict_7bit_headers (no)\fR"
+/* Reject mail with 8-bit text in message headers.
+/* .IP "\fBstrict_8bitmime_body (no)\fR"
+/* Reject 8-bit message body text without 8-bit MIME content encoding
+/* information.
+/* .IP "\fBstrict_mime_encoding_domain (no)\fR"
+/* Reject mail with invalid Content-Transfer-Encoding: information
+/* for the message/* or multipart/* MIME content types.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBdetect_8bit_encoding_header (yes)\fR"
+/* Automatically detect 8BITMIME body content by looking at
+/* Content-Transfer-Encoding: message headers; historically, this
+/* behavior was hard-coded to be "always on".
+/* AUTOMATIC BCC RECIPIENT CONTROLS
+/* .ad
+/* .fi
+/* Postfix can automatically add BCC (blind carbon copy)
+/* when mail enters the mail system:
+/* .IP "\fBalways_bcc (empty)\fR"
+/* Optional address that receives a "blind carbon copy" of each message
+/* that is received by the Postfix mail system.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBsender_bcc_maps (empty)\fR"
+/* Optional BCC (blind carbon-copy) address lookup tables, indexed
+/* by sender address.
+/* .IP "\fBrecipient_bcc_maps (empty)\fR"
+/* Optional BCC (blind carbon-copy) address lookup tables, indexed by
+/* recipient address.
+/* ADDRESS TRANSFORMATION CONTROLS
+/* .ad
+/* .fi
+/* Address rewriting is delegated to the \fBtrivial-rewrite\fR(8) daemon.
+/* The \fBcleanup\fR(8) server implements table driven address mapping.
+/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+/* The recipient of mail addressed to the null address.
+/* .IP "\fBcanonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for message headers and
+/* envelopes.
+/* .IP "\fBrecipient_canonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for envelope and header
+/* recipient addresses.
+/* .IP "\fBsender_canonical_maps (empty)\fR"
+/* Optional address mapping lookup tables for envelope and header
+/* sender addresses.
+/* .IP "\fBmasquerade_classes (envelope_sender, header_sender, header_recipient)\fR"
+/* What addresses are subject to address masquerading.
+/* .IP "\fBmasquerade_domains (empty)\fR"
+/* Optional list of domains whose subdomain structure will be stripped
+/* off in email addresses.
+/* .IP "\fBmasquerade_exceptions (empty)\fR"
+/* Optional list of user names that are not subjected to address
+/* masquerading, even when their addresses match $masquerade_domains.
+/* .IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+/* What address lookup tables copy an address extension from the lookup
+/* key to the lookup result.
+/* .PP
+/* Available before Postfix version 2.0:
+/* .IP "\fBvirtual_maps (empty)\fR"
+/* Optional lookup tables with a) names of domains for which all
+/* addresses are aliased to addresses in other local or remote domains,
+/* and b) addresses that are aliased to addresses in other local or
+/* remote domains.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
+/* Optional lookup tables that alias specific mail addresses or domains
+/* to other local or remote address.
+/* .PP
+/* Available in Postfix version 2.2 and later:
+/* .IP "\fBcanonical_classes (envelope_sender, envelope_recipient, header_sender, header_recipient)\fR"
+/* What addresses are subject to canonical_maps address mapping.
+/* .IP "\fBrecipient_canonical_classes (envelope_recipient, header_recipient)\fR"
+/* What addresses are subject to recipient_canonical_maps address
+/* mapping.
+/* .IP "\fBsender_canonical_classes (envelope_sender, header_sender)\fR"
+/* What addresses are subject to sender_canonical_maps address
+/* mapping.
+/* .IP "\fBremote_header_rewrite_domain (empty)\fR"
+/* Don't rewrite message headers from remote clients at all when
+/* this parameter is empty; otherwise, rewrite message headers and
+/* append the specified domain name to incomplete addresses.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBduplicate_filter_limit (1000)\fR"
+/* The maximal number of addresses remembered by the address
+/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or
+/* for \fBshowq\fR(8) queue displays.
+/* .IP "\fBheader_size_limit (102400)\fR"
+/* The maximal amount of memory in bytes for storing a message header.
+/* .IP "\fBhopcount_limit (50)\fR"
+/* The maximal number of Received: message headers that is allowed
+/* in the primary message headers.
+/* .IP "\fBin_flow_delay (1s)\fR"
+/* Time to pause before accepting a new message, when the message
+/* arrival rate exceeds the message delivery rate.
+/* .IP "\fBmessage_size_limit (10240000)\fR"
+/* The maximal size in bytes of a message, including envelope information.
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBheader_address_token_limit (10240)\fR"
+/* The maximal number of address tokens are allowed in an address
+/* message header.
+/* .IP "\fBmime_boundary_length_limit (2048)\fR"
+/* The maximal length of MIME multipart boundary strings.
+/* .IP "\fBmime_nesting_limit (100)\fR"
+/* The maximal recursion level that the MIME processor will handle.
+/* .IP "\fBqueue_file_attribute_count_limit (100)\fR"
+/* The maximal number of (name=value) attributes that may be stored
+/* in a Postfix queue file.
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBvirtual_alias_expansion_limit (1000)\fR"
+/* The maximal number of addresses that virtual alias expansion produces
+/* from each original recipient.
+/* .IP "\fBvirtual_alias_recursion_limit (1000)\fR"
+/* The maximal nesting depth of virtual alias expansion.
+/* .PP
+/* Available in Postfix version 3.0 and later:
+/* .IP "\fBvirtual_alias_address_length_limit (1000)\fR"
+/* The maximal length of an email address after virtual alias expansion.
+/* SMTPUTF8 CONTROLS
+/* .ad
+/* .fi
+/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+/* .IP "\fBsmtputf8_enable (yes)\fR"
+/* Enable preliminary SMTPUTF8 support for the protocols described
+/* in RFC 6531..6533.
+/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
+/* Detect that a message requires SMTPUTF8 support for the specified
+/* mail origin classes.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBenable_idna2003_compatibility (no)\fR"
+/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+/* when converting UTF-8 domain names to/from the ASCII form that is
+/* used for DNS lookups.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBdelay_logging_resolution_limit (2)\fR"
+/* The maximal number of digits after the decimal point when logging
+/* sub-second delay values.
+/* .IP "\fBdelay_warning_time (0h)\fR"
+/* The time after which the sender receives a copy of the message
+/* headers of mail that is still queued.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBmyhostname (see 'postconf -d' output)\fR"
+/* The internet hostname of this mail system.
+/* .IP "\fBmyorigin ($myhostname)\fR"
+/* The domain name that locally-posted mail appears to come
+/* from, and that locally posted mail is delivered to.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix version 2.1 and later:
+/* .IP "\fBenable_original_recipient (yes)\fR"
+/* Enable support for the original recipient address after an
+/* address is rewritten to a different address (for example with
+/* aliasing or with canonical mapping).
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* .PP
+/* Available in Postfix 3.5 and later:
+/* .IP "\fBinfo_log_address_format (external)\fR"
+/* The email address form that will be used in non-debug logging
+/* (info, warning, etc.).
+/* FILES
+/* /etc/postfix/canonical*, canonical mapping table
+/* /etc/postfix/virtual*, virtual mapping table
+/* SEE ALSO
+/* trivial-rewrite(8), address rewriting
+/* qmgr(8), queue manager
+/* header_checks(5), message header content inspection
+/* body_checks(5), body parts content inspection
+/* canonical(5), canonical address lookup table format
+/* virtual(5), virtual alias lookup table format
+/* postconf(5), configuration parameters
+/* master(5), generic daemon options
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* ADDRESS_REWRITING_README Postfix address manipulation
+/* CONTENT_INSPECTION_README content inspection
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_conf.h>
+#include <cleanup_user.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_version.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_service - process one request to inject a message into the queue */
+
+static void cleanup_service(VSTREAM *src, char *unused_service, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ CLEANUP_STATE *state;
+ int flags;
+ int type = 0;
+ int status;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Open a queue file and initialize state.
+ */
+ state = cleanup_open(src);
+
+ /*
+ * Send the queue id to the client. Read client processing options. If we
+ * can't read the client processing options we can pretty much forget
+ * about the whole operation.
+ */
+ attr_print(src, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, state->queue_id),
+ ATTR_TYPE_END);
+ if (attr_scan(src, ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags),
+ ATTR_TYPE_END) != 1) {
+ state->errs |= CLEANUP_STAT_BAD;
+ flags = 0;
+ }
+ cleanup_control(state, flags);
+
+ /*
+ * XXX Rely on the front-end programs to enforce record size limits.
+ *
+ * First, copy the envelope records to the queue file. Then, copy the
+ * message content (headers and body). Finally, attach any information
+ * extracted from message headers.
+ */
+ while (CLEANUP_OUT_OK(state)) {
+ if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) {
+ state->errs |= CLEANUP_STAT_BAD;
+ break;
+ }
+ if (REC_GET_HIDDEN_TYPE(type)) {
+ msg_warn("%s: record type %d not allowed - discarding this message",
+ state->queue_id, type);
+ state->errs |= CLEANUP_STAT_BAD;
+ break;
+ }
+ CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf));
+ if (type == REC_TYPE_END)
+ break;
+ }
+
+ /*
+ * Keep reading in case of problems, until the sender is ready to receive
+ * our status report.
+ */
+ if (CLEANUP_OUT_OK(state) == 0 && type > 0) {
+ while (type != REC_TYPE_END
+ && (type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) > 0) {
+ if (type == REC_TYPE_MILT_COUNT) {
+ int milter_count = atoi(vstring_str(buf));
+
+ /* Avoid deadlock. */
+ if (milter_count >= 0)
+ cleanup_milter_receive(state, milter_count);
+ }
+ }
+ }
+
+ /*
+ * Log something to make timeout errors easier to debug.
+ */
+ if (vstream_ftimeout(src))
+ msg_warn("%s: read timeout on %s",
+ state->queue_id, VSTREAM_PATH(src));
+
+ /*
+ * Finish this message, and report the result status to the client.
+ */
+ status = cleanup_flush(state); /* in case state is modified */
+ attr_print(src, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
+ SEND_ATTR_STR(MAIL_ATTR_WHY,
+ (state->flags & CLEANUP_FLAG_SMTP_REPLY)
+ && state->smtp_reply ? state->smtp_reply :
+ state->reason ? state->reason : ""),
+ ATTR_TYPE_END);
+ cleanup_free(state);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Clean up an incomplete queue file in case of a fatal run-time error,
+ * or after receiving SIGTERM from the master at shutdown time.
+ */
+ signal(SIGTERM, cleanup_sig);
+ msg_cleanup(cleanup_all);
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, cleanup_service,
+ CA_MAIL_SERVER_INT_TABLE(cleanup_int_table),
+ CA_MAIL_SERVER_BOOL_TABLE(cleanup_bool_table),
+ CA_MAIL_SERVER_STR_TABLE(cleanup_str_table),
+ CA_MAIL_SERVER_TIME_TABLE(cleanup_time_table),
+ CA_MAIL_SERVER_PRE_INIT(cleanup_pre_jail),
+ CA_MAIL_SERVER_POST_INIT(cleanup_post_jail),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_IN_FLOW_DELAY,
+ CA_MAIL_SERVER_UNLIMITED,
+ 0);
+}
diff --git a/src/cleanup/cleanup.h b/src/cleanup/cleanup.h
new file mode 100644
index 0000000..df88a70
--- /dev/null
+++ b/src/cleanup/cleanup.h
@@ -0,0 +1,372 @@
+/*++
+/* NAME
+/* cleanup 3h
+/* SUMMARY
+/* canonicalize and enqueue message
+/* SYNOPSIS
+/* #include "cleanup.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <nvtable.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+#include <tok822.h>
+#include <been_here.h>
+#include <mail_stream.h>
+#include <mail_conf.h>
+#include <mime_state.h>
+#include <string_list.h>
+#include <cleanup_user.h>
+#include <header_body_checks.h>
+#include <dsn_mask.h>
+
+ /*
+ * Milter library.
+ */
+#include <milter.h>
+
+ /*
+ * These state variables are accessed by many functions, and there is only
+ * one instance of each per message.
+ */
+typedef struct CLEANUP_STATE {
+ VSTRING *attr_buf; /* storage for named attribute */
+ VSTRING *temp1; /* scratch buffer, local use only */
+ VSTRING *temp2; /* scratch buffer, local use only */
+ VSTRING *stripped_buf; /* character stripped input */
+ VSTREAM *src; /* current input stream */
+ VSTREAM *dst; /* current output stream */
+ MAIL_STREAM *handle; /* mail stream handle */
+ char *queue_name; /* queue name */
+ char *queue_id; /* queue file basename */
+ struct timeval arrival_time; /* arrival time */
+ char *fullname; /* envelope sender full name */
+ char *sender; /* envelope sender address */
+ char *recip; /* envelope recipient address */
+ char *orig_rcpt; /* original recipient address */
+ char *return_receipt; /* return-receipt address */
+ char *errors_to; /* errors-to address */
+ ARGV *auto_hdrs; /* MTA's own header(s) */
+ ARGV *hbc_rcpt; /* header/body checks BCC addresses */
+ int flags; /* processing options, status flags */
+ int tflags; /* User- or MTA-requested tracing */
+ int qmgr_opts; /* qmgr processing options */
+ int errs; /* any badness experienced */
+ int err_mask; /* allowed badness */
+ int headers_seen; /* which headers were seen */
+ int hop_count; /* count of received: headers */
+ char *resent; /* any resent- header seen */
+ BH_TABLE *dups; /* recipient dup filter */
+ void (*action) (struct CLEANUP_STATE *, int, const char *, ssize_t);
+ off_t data_offset; /* start of message content */
+ off_t body_offset; /* start of body content */
+ off_t xtra_offset; /* start of extra segment */
+ off_t cont_length; /* length including Milter edits */
+ off_t sender_pt_offset; /* replace sender here */
+ off_t sender_pt_target; /* record after sender address */
+ off_t append_rcpt_pt_offset; /* append recipient here */
+ off_t append_rcpt_pt_target; /* target of above record */
+ off_t append_hdr_pt_offset; /* append header here */
+ off_t append_hdr_pt_target; /* target of above record */
+ off_t append_meta_pt_offset; /* append meta record here */
+ off_t append_meta_pt_target; /* target of above record */
+ ssize_t rcpt_count; /* recipient count */
+ char *reason; /* failure reason */
+ char *smtp_reply; /* failure reason, SMTP-style */
+ NVTABLE *attr; /* queue file attribute list */
+ MIME_STATE *mime_state; /* MIME state engine */
+ int mime_errs; /* MIME error flags */
+ char *hdr_rewrite_context; /* header rewrite context */
+ char *filter; /* from header/body patterns */
+ char *redirect; /* from header/body patterns */
+ char *dsn_envid; /* DSN envelope ID */
+ int dsn_ret; /* DSN full/hdrs */
+ int dsn_notify; /* DSN never/delay/fail/success */
+ char *dsn_orcpt; /* DSN original recipient */
+ char *verp_delims; /* VERP delimiters (optional) */
+#ifdef DELAY_ACTION
+ int defer_delay; /* deferred delivery */
+#endif
+
+ /*
+ * Miscellaneous Milter support.
+ */
+ MILTERS *milters; /* mail filters */
+ const char *client_name; /* real or ersatz client */
+ const char *reverse_name; /* real or ersatz client */
+ const char *client_addr; /* real or ersatz client */
+ int client_af; /* real or ersatz client */
+ const char *client_port; /* real or ersatz client */
+ const char *server_addr; /* real or ersatz server */
+ const char *server_port; /* real or ersatz server */
+ VSTRING *milter_ext_from; /* externalized sender */
+ VSTRING *milter_ext_rcpt; /* externalized recipient */
+ VSTRING *milter_err_text; /* milter call-back reply */
+ VSTRING *milter_dsn_buf; /* Milter DSN parsing buffer */
+
+ /*
+ * Support for Milter body replacement requests.
+ */
+ struct CLEANUP_REGION *free_regions;/* unused regions */
+ struct CLEANUP_REGION *body_regions;/* regions with body content */
+ struct CLEANUP_REGION *curr_body_region;
+
+ /*
+ * Internationalization.
+ */
+ int smtputf8; /* what support is desired */
+} CLEANUP_STATE;
+
+ /*
+ * Status flags. Flags 0-15 are reserved for cleanup_user.h.
+ */
+#define CLEANUP_FLAG_INRCPT (1<<16) /* Processing recipient records */
+#define CLEANUP_FLAG_WARN_SEEN (1<<17) /* REC_TYPE_WARN record seen */
+#define CLEANUP_FLAG_END_SEEN (1<<18) /* REC_TYPE_END record seen */
+
+ /*
+ * Mappings.
+ */
+extern MAPS *cleanup_comm_canon_maps;
+extern MAPS *cleanup_send_canon_maps;
+extern MAPS *cleanup_rcpt_canon_maps;
+extern int cleanup_comm_canon_flags;
+extern int cleanup_send_canon_flags;
+extern int cleanup_rcpt_canon_flags;
+extern MAPS *cleanup_header_checks;
+extern MAPS *cleanup_mimehdr_checks;
+extern MAPS *cleanup_nesthdr_checks;
+extern MAPS *cleanup_body_checks;
+extern MAPS *cleanup_virt_alias_maps;
+extern ARGV *cleanup_masq_domains;
+extern STRING_LIST *cleanup_masq_exceptions;
+extern int cleanup_masq_flags;
+extern MAPS *cleanup_send_bcc_maps;
+extern MAPS *cleanup_rcpt_bcc_maps;
+
+ /*
+ * Character filters.
+ */
+extern VSTRING *cleanup_reject_chars;
+extern VSTRING *cleanup_strip_chars;
+
+ /*
+ * Milters.
+ */
+extern MILTERS *cleanup_milters;
+
+ /*
+ * Address canonicalization fine control.
+ */
+#define CLEANUP_CANON_FLAG_ENV_FROM (1<<0) /* envelope sender */
+#define CLEANUP_CANON_FLAG_ENV_RCPT (1<<1) /* envelope recipient */
+#define CLEANUP_CANON_FLAG_HDR_FROM (1<<2) /* header sender */
+#define CLEANUP_CANON_FLAG_HDR_RCPT (1<<3) /* header recipient */
+
+ /*
+ * Address masquerading fine control.
+ */
+#define CLEANUP_MASQ_FLAG_ENV_FROM (1<<0) /* envelope sender */
+#define CLEANUP_MASQ_FLAG_ENV_RCPT (1<<1) /* envelope recipient */
+#define CLEANUP_MASQ_FLAG_HDR_FROM (1<<2) /* header sender */
+#define CLEANUP_MASQ_FLAG_HDR_RCPT (1<<3) /* header recipient */
+
+ /*
+ * Restrictions on extension propagation.
+ */
+extern int cleanup_ext_prop_mask;
+
+ /*
+ * Saved queue file names, so the files can be removed in case of a fatal
+ * run-time error.
+ */
+extern char *cleanup_path;
+extern VSTRING *cleanup_trace_path;
+extern VSTRING *cleanup_bounce_path;
+
+ /*
+ * cleanup_state.c
+ */
+extern CLEANUP_STATE *cleanup_state_alloc(VSTREAM *);
+extern void cleanup_state_free(CLEANUP_STATE *);
+
+ /*
+ * cleanup_api.c
+ */
+extern CLEANUP_STATE *cleanup_open(VSTREAM *);
+extern void cleanup_control(CLEANUP_STATE *, int);
+extern int cleanup_flush(CLEANUP_STATE *);
+extern void cleanup_free(CLEANUP_STATE *);
+extern void cleanup_all(void);
+extern void cleanup_sig(int);
+extern void cleanup_pre_jail(char *, char **);
+extern void cleanup_post_jail(char *, char **);
+extern const CONFIG_INT_TABLE cleanup_int_table[];
+extern const CONFIG_BOOL_TABLE cleanup_bool_table[];
+extern const CONFIG_STR_TABLE cleanup_str_table[];
+extern const CONFIG_TIME_TABLE cleanup_time_table[];
+
+#define CLEANUP_RECORD(s, t, b, l) ((s)->action((s), (t), (b), (l)))
+
+ /*
+ * cleanup_out.c
+ */
+extern void cleanup_out(CLEANUP_STATE *, int, const char *, ssize_t);
+extern void cleanup_out_string(CLEANUP_STATE *, int, const char *);
+extern void PRINTFLIKE(3, 4) cleanup_out_format(CLEANUP_STATE *, int, const char *,...);
+extern void cleanup_out_header(CLEANUP_STATE *, VSTRING *);
+
+#define CLEANUP_OUT_BUF(s, t, b) \
+ cleanup_out((s), (t), vstring_str((b)), VSTRING_LEN((b)))
+
+#define CLEANUP_OUT_OK(s) \
+ (!((s)->errs & (s)->err_mask) && !((s)->flags & CLEANUP_FLAG_DISCARD))
+
+ /*
+ * cleanup_envelope.c
+ */
+extern void cleanup_envelope(CLEANUP_STATE *, int, const char *, ssize_t);
+
+ /*
+ * cleanup_message.c
+ */
+extern void cleanup_message(CLEANUP_STATE *, int, const char *, ssize_t);
+
+ /*
+ * cleanup_extracted.c
+ */
+extern void cleanup_extracted(CLEANUP_STATE *, int, const char *, ssize_t);
+
+ /*
+ * cleanup_final.c
+ */
+extern void cleanup_final(CLEANUP_STATE *);
+
+ /*
+ * cleanup_rewrite.c
+ */
+extern int cleanup_rewrite_external(const char *, VSTRING *, const char *);
+extern int cleanup_rewrite_internal(const char *, VSTRING *, const char *);
+extern int cleanup_rewrite_tree(const char *, TOK822 *);
+
+ /*
+ * cleanup_map11.c
+ */
+extern int cleanup_map11_external(CLEANUP_STATE *, VSTRING *, MAPS *, int);
+extern int cleanup_map11_internal(CLEANUP_STATE *, VSTRING *, MAPS *, int);
+extern int cleanup_map11_tree(CLEANUP_STATE *, TOK822 *, MAPS *, int);
+
+ /*
+ * cleanup_map1n.c
+ */
+ARGV *cleanup_map1n_internal(CLEANUP_STATE *, const char *, MAPS *, int);
+
+ /*
+ * cleanup_masquerade.c
+ */
+extern int cleanup_masquerade_external(CLEANUP_STATE *, VSTRING *, ARGV *);
+extern int cleanup_masquerade_internal(CLEANUP_STATE *, VSTRING *, ARGV *);
+extern int cleanup_masquerade_tree(CLEANUP_STATE *, TOK822 *, ARGV *);
+
+ /*
+ * cleanup_recipient.c
+ */
+extern void cleanup_out_recipient(CLEANUP_STATE *, const char *, int, const char *, const char *);
+
+ /*
+ * cleanup_addr.c.
+ */
+extern off_t cleanup_addr_sender(CLEANUP_STATE *, const char *);
+extern void cleanup_addr_recipient(CLEANUP_STATE *, const char *);
+extern void cleanup_addr_bcc_dsn(CLEANUP_STATE *, const char *, const char *, int);
+
+#define NO_DSN_ORCPT ((char *) 0)
+#define NO_DSN_NOTIFY DSN_NOTIFY_NEVER
+#define DEF_DSN_NOTIFY (0)
+
+#define cleanup_addr_bcc(state, addr) \
+ cleanup_addr_bcc_dsn((state), (addr), NO_DSN_ORCPT, NO_DSN_NOTIFY)
+
+ /*
+ * cleanup_bounce.c.
+ */
+extern int cleanup_bounce(CLEANUP_STATE *);
+
+ /*
+ * MSG_STATS compatibility.
+ */
+#define CLEANUP_MSG_STATS(stats, state) \
+ MSG_STATS_INIT1(stats, incoming_arrival, state->arrival_time)
+
+ /*
+ * cleanup_milter.c.
+ */
+extern void cleanup_milter_header_checks_init(void);
+extern void cleanup_milter_receive(CLEANUP_STATE *, int);
+extern void cleanup_milter_inspect(CLEANUP_STATE *, MILTERS *);
+extern void cleanup_milter_emul_mail(CLEANUP_STATE *, MILTERS *, const char *);
+extern void cleanup_milter_emul_rcpt(CLEANUP_STATE *, MILTERS *, const char *);
+extern void cleanup_milter_emul_data(CLEANUP_STATE *, MILTERS *);
+
+#define CLEANUP_MILTER_OK(s) \
+ (((s)->flags & CLEANUP_FLAG_MILTER) != 0 \
+ && (s)->errs == 0 && ((s)->flags & CLEANUP_FLAG_DISCARD) == 0)
+
+ /*
+ * cleanup_body_edit.c
+ */
+typedef struct CLEANUP_REGION {
+ off_t start; /* start of region */
+ off_t len; /* length or zero (open-ended) */
+ off_t write_offs; /* write offset */
+ struct CLEANUP_REGION *next; /* linkage */
+} CLEANUP_REGION;
+
+extern void cleanup_region_init(CLEANUP_STATE *);
+extern CLEANUP_REGION *cleanup_region_open(CLEANUP_STATE *, ssize_t);
+extern void cleanup_region_close(CLEANUP_STATE *, CLEANUP_REGION *);
+extern CLEANUP_REGION *cleanup_region_return(CLEANUP_STATE *, CLEANUP_REGION *);
+extern void cleanup_region_done(CLEANUP_STATE *);
+
+extern int cleanup_body_edit_start(CLEANUP_STATE *);
+extern int cleanup_body_edit_write(CLEANUP_STATE *, int, VSTRING *);
+extern int cleanup_body_edit_finish(CLEANUP_STATE *);
+extern void cleanup_body_edit_free(CLEANUP_STATE *);
+
+ /*
+ * From: header formatting.
+ */
+#define HFROM_FORMAT_CODE_STD 0
+#define HFROM_FORMAT_CODE_OBS 1
+extern int hfrom_format_code;
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/cleanup/cleanup_addr.c b/src/cleanup/cleanup_addr.c
new file mode 100644
index 0000000..fd8a511
--- /dev/null
+++ b/src/cleanup/cleanup_addr.c
@@ -0,0 +1,286 @@
+/*++
+/* NAME
+/* cleanup_addr 3
+/* SUMMARY
+/* process envelope addresses
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* off_t cleanup_addr_sender(state, addr)
+/* CLEANUP_STATE *state;
+/* const char *addr;
+/*
+/* void cleanup_addr_recipient(state, addr)
+/* CLEANUP_STATE *state;
+/* const char *addr;
+/*
+/* void cleanup_addr_bcc_dsn(state, addr, dsn_orcpt, dsn_notify)
+/* CLEANUP_STATE *state;
+/* const char *addr;
+/* const char *dsn_orcpt;
+/* int dsn_notify;
+/*
+/* void cleanup_addr_bcc(state, addr)
+/* CLEANUP_STATE *state;
+/* const char *addr;
+/* DESCRIPTION
+/* This module processes envelope address records and writes the result
+/* to the queue file. Processing includes address rewriting and
+/* sender/recipient auto bcc address generation.
+/*
+/* cleanup_addr_sender() processes sender envelope information and updates
+/* state->sender. The result value is the offset of the record that
+/* follows the sender record if milters are enabled, otherwise zero.
+/*
+/* cleanup_addr_recipient() processes recipient envelope information
+/* and updates state->recip.
+/*
+/* cleanup_addr_bcc_dsn() processes recipient envelope information. This
+/* is a separate function to avoid invoking cleanup_addr_recipient()
+/* recursively.
+/*
+/* cleanup_addr_bcc() is a backwards-compatibility wrapper for
+/* cleanup_addr_bcc_dsn() that requests no delivery status
+/* notification for the recipient.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is updated
+/* as records are processed and as errors happen.
+/* .IP buf
+/* Record content.
+/* .IP dsn_orcpt
+/* The DSN original recipient (or NO_DSN_ORCPT to specify none).
+/* .IP dsn_notify
+/* DSN notification options. Specify NO_DSN_NOTIFY to disable
+/* notification, and DEF_DSN_NOTIFY for default notification.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <rec_type.h>
+#include <record.h>
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <ext_prop.h>
+#include <mail_addr.h>
+#include <canon_addr.h>
+#include <mail_addr_find.h>
+#include <mail_proto.h>
+#include <dsn_mask.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+#define IGNORE_EXTENSION (char **) 0
+
+/* cleanup_addr_sender - process envelope sender record */
+
+off_t cleanup_addr_sender(CLEANUP_STATE *state, const char *buf)
+{
+ const char myname[] = "cleanup_addr_sender";
+ VSTRING *clean_addr = vstring_alloc(100);
+ off_t after_sender_offs = 0;
+ const char *bcc;
+ size_t len;
+
+ /*
+ * Note: an unqualified envelope address is for all practical purposes
+ * equivalent to a fully qualified local address, both for delivery and
+ * for replying. Having to support both forms is error prone, therefore
+ * an incomplete envelope address is rewritten to fully qualified form in
+ * the local domain context.
+ *
+ * 20000520: Replace mailer-daemon@$myorigin by the null address, to handle
+ * bounced mail traffic more robustly.
+ */
+ cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL, clean_addr, buf);
+ if (strncasecmp_utf8(STR(clean_addr), MAIL_ADDR_MAIL_DAEMON "@",
+ sizeof(MAIL_ADDR_MAIL_DAEMON)) == 0) {
+ canon_addr_internal(state->temp1, MAIL_ADDR_MAIL_DAEMON);
+ if (strcasecmp_utf8(STR(clean_addr), STR(state->temp1)) == 0)
+ vstring_strcpy(clean_addr, "");
+ }
+ if (state->flags & CLEANUP_FLAG_MAP_OK) {
+ if (cleanup_send_canon_maps
+ && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_ENV_FROM))
+ cleanup_map11_internal(state, clean_addr, cleanup_send_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps
+ && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_FROM))
+ cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_masq_domains
+ && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_FROM))
+ cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains);
+ }
+ /* Fix 20140711: Auto-detect an UTF8 sender. */
+ if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
+ && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ state->smtputf8 |= SMTPUTF8_FLAG_SENDER;
+ /* Fix 20140713: request SMTPUTF8 support selectively. */
+ if (state->flags & CLEANUP_FLAG_AUTOUTF8)
+ state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
+ }
+ CLEANUP_OUT_BUF(state, REC_TYPE_FROM, clean_addr);
+ if (state->sender) /* XXX Can't happen */
+ myfree(state->sender);
+ state->sender = mystrdup(STR(clean_addr)); /* Used by Milter client */
+ /* Fix 20160310: Moved from cleanup_envelope.c. */
+ if (state->milters || cleanup_milters) {
+ /* Make room to replace sender. */
+ if ((len = LEN(clean_addr)) < REC_TYPE_PTR_PAYL_SIZE)
+ rec_pad(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_PAYL_SIZE - len);
+ /* Remember the after-sender record offset. */
+ if ((after_sender_offs = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ if ((state->flags & CLEANUP_FLAG_BCC_OK)
+ && *STR(clean_addr)
+ && cleanup_send_bcc_maps) {
+ if ((bcc = mail_addr_find_to_internal(cleanup_send_bcc_maps,
+ STR(clean_addr),
+ IGNORE_EXTENSION)) != 0) {
+ cleanup_addr_bcc(state, bcc);
+ } else if (cleanup_send_bcc_maps->error) {
+ msg_warn("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, cleanup_send_bcc_maps->title);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+ vstring_free(clean_addr);
+ return after_sender_offs;
+}
+
+/* cleanup_addr_recipient - process envelope recipient */
+
+void cleanup_addr_recipient(CLEANUP_STATE *state, const char *buf)
+{
+ VSTRING *clean_addr = vstring_alloc(100);
+ const char *bcc;
+
+ /*
+ * Note: an unqualified envelope address is for all practical purposes
+ * equivalent to a fully qualified local address, both for delivery and
+ * for replying. Having to support both forms is error prone, therefore
+ * an incomplete envelope address is rewritten to fully qualified form in
+ * the local domain context.
+ */
+ cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL,
+ clean_addr, *buf ? buf : var_empty_addr);
+ if (state->flags & CLEANUP_FLAG_MAP_OK) {
+ if (cleanup_rcpt_canon_maps
+ && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT))
+ cleanup_map11_internal(state, clean_addr, cleanup_rcpt_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps
+ && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT))
+ cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_masq_domains
+ && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT))
+ cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains);
+ }
+ /* Fix 20140711: Auto-detect an UTF8 recipient. */
+ if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
+ && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ /* Fix 20140713: request SMTPUTF8 support selectively. */
+ if (state->flags & CLEANUP_FLAG_AUTOUTF8)
+ state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
+ }
+ /* Fix 20141024: Don't fake up a "bare" DSN original rcpt in smtp(8). */
+ if (state->dsn_orcpt == 0 && *STR(clean_addr) != 0)
+ state->dsn_orcpt = concatenate((!allascii(STR(clean_addr))
+ && (state->smtputf8 & SMTPUTF8_FLAG_REQUESTED)) ?
+ "utf-8" : "rfc822", ";", STR(clean_addr), (char *) 0);
+ cleanup_out_recipient(state, state->dsn_orcpt, state->dsn_notify,
+ state->orig_rcpt, STR(clean_addr));
+ if (state->recip) /* This can happen */
+ myfree(state->recip);
+ state->recip = mystrdup(STR(clean_addr)); /* Used by Milter client */
+ if ((state->flags & CLEANUP_FLAG_BCC_OK)
+ && *STR(clean_addr)
+ && cleanup_rcpt_bcc_maps) {
+ if ((bcc = mail_addr_find_to_internal(cleanup_rcpt_bcc_maps,
+ STR(clean_addr),
+ IGNORE_EXTENSION)) != 0) {
+ cleanup_addr_bcc(state, bcc);
+ } else if (cleanup_rcpt_bcc_maps->error) {
+ msg_warn("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, cleanup_rcpt_bcc_maps->title);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+ vstring_free(clean_addr);
+}
+
+/* cleanup_addr_bcc_dsn - process automatic BCC recipient */
+
+void cleanup_addr_bcc_dsn(CLEANUP_STATE *state, const char *bcc,
+ const char *dsn_orcpt, int dsn_notify)
+{
+ VSTRING *clean_addr = vstring_alloc(100);
+
+ /*
+ * Note: BCC addresses are supplied locally, and must be rewritten in the
+ * local address rewriting context.
+ */
+ cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL, clean_addr, bcc);
+ if (state->flags & CLEANUP_FLAG_MAP_OK) {
+ if (cleanup_rcpt_canon_maps
+ && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT))
+ cleanup_map11_internal(state, clean_addr, cleanup_rcpt_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps
+ && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT))
+ cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_masq_domains
+ && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT))
+ cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains);
+ }
+ /* Fix 20140711: Auto-detect an UTF8 recipient. */
+ if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
+ && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ /* Fix 20140713: request SMTPUTF8 support selectively. */
+ if (state->flags & CLEANUP_FLAG_AUTOUTF8)
+ state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
+ }
+ cleanup_out_recipient(state, dsn_orcpt, dsn_notify,
+ STR(clean_addr), STR(clean_addr));
+ vstring_free(clean_addr);
+}
diff --git a/src/cleanup/cleanup_api.c b/src/cleanup/cleanup_api.c
new file mode 100644
index 0000000..4fc5e2e
--- /dev/null
+++ b/src/cleanup/cleanup_api.c
@@ -0,0 +1,395 @@
+/*++
+/* NAME
+/* cleanup_api 3
+/* SUMMARY
+/* cleanup callable interface, message processing
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* CLEANUP_STATE *cleanup_open(src)
+/* VSTREAM *src;
+/*
+/* void cleanup_control(state, flags)
+/* CLEANUP_STATE *state;
+/* int flags;
+/*
+/* void CLEANUP_RECORD(state, type, buf, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* char *buf;
+/* int len;
+/*
+/* int cleanup_flush(state)
+/* CLEANUP_STATE *state;
+/*
+/* int cleanup_free(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* This module implements a callable interface to the cleanup service
+/* for processing one message and for writing it to queue file.
+/* For a description of the cleanup service, see cleanup(8).
+/*
+/* cleanup_open() creates a new queue file and performs other
+/* per-message initialization. The result is a handle that should be
+/* given to the cleanup_control(), cleanup_record(), cleanup_flush()
+/* and cleanup_free() routines. The name of the queue file is in the
+/* queue_id result structure member.
+/*
+/* cleanup_control() processes per-message flags specified by the caller.
+/* These flags control the handling of data errors, and must be set
+/* before processing the first message record.
+/* .IP CLEANUP_FLAG_BOUNCE
+/* The cleanup server is responsible for returning undeliverable
+/* mail (too many hops, message too large) to the sender.
+/* .IP CLEANUP_FLAG_BCC_OK
+/* It is OK to add automatic BCC recipient addresses.
+/* .IP CLEANUP_FLAG_FILTER
+/* Enable header/body filtering. This should be enabled only with mail
+/* that enters Postfix, not with locally forwarded mail or with bounce
+/* messages.
+/* .IP CLEANUP_FLAG_MILTER
+/* Enable Milter applications. This should be enabled only with mail
+/* that enters Postfix, not with locally forwarded mail or with bounce
+/* messages.
+/* .IP CLEANUP_FLAG_MAP_OK
+/* Enable canonical and virtual mapping, and address masquerading.
+/* .PP
+/* For convenience the CLEANUP_FLAG_MASK_EXTERNAL macro specifies
+/* the options that are normally needed for mail that enters
+/* Postfix from outside, and CLEANUP_FLAG_MASK_INTERNAL specifies
+/* the options that are normally needed for internally generated or
+/* forwarded mail.
+/*
+/* CLEANUP_RECORD() is a macro that processes one message record,
+/* that copies the result to the queue file, and that maintains a
+/* little state machine. The last record in a valid message has type
+/* REC_TYPE_END. In order to find out if a message is corrupted,
+/* the caller is encouraged to test the CLEANUP_OUT_OK(state) macro.
+/* The result is false when further message processing is futile.
+/* In that case, it is safe to call cleanup_flush() immediately.
+/*
+/* cleanup_flush() closes a queue file. In case of any errors,
+/* the file is removed. The result value is non-zero in case of
+/* problems. In some cases a human-readable text can be found in
+/* the state->reason member. In all other cases, use cleanup_strerror()
+/* to translate the result into human-readable text.
+/*
+/* cleanup_free() destroys its argument.
+/* .IP CLEANUP_FLAG_SMTPUTF8
+/* Request SMTPUTF8 support when delivering mail.
+/* .IP CLEANUP_FLAG_AUTOUTF8
+/* Autodetection: request SMTPUTF8 support if the message
+/* contains an UTF8 message header, sender, or recipient.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* cleanup(8) cleanup service description.
+/* cleanup_init(8) cleanup callable interface, initialization
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_queue.h>
+#include <mail_proto.h>
+#include <bounce.h>
+#include <mail_params.h>
+#include <mail_stream.h>
+#include <mail_flow.h>
+#include <rec_type.h>
+#include <smtputf8.h>
+
+/* Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_open - open queue file and initialize */
+
+CLEANUP_STATE *cleanup_open(VSTREAM *src)
+{
+ CLEANUP_STATE *state;
+ static const char *log_queues[] = {
+ MAIL_QUEUE_DEFER,
+ MAIL_QUEUE_BOUNCE,
+ MAIL_QUEUE_TRACE,
+ 0,
+ };
+ const char **cpp;
+
+ /*
+ * Initialize private state.
+ */
+ state = cleanup_state_alloc(src);
+
+ /*
+ * Open the queue file. Save the queue file name in a global variable, so
+ * that the runtime error handler can clean up in case of problems.
+ *
+ * XXX For now, a lot of detail is frozen that could be more useful if it
+ * were made configurable.
+ */
+ state->queue_name = mystrdup(MAIL_QUEUE_INCOMING);
+ state->handle = mail_stream_file(state->queue_name,
+ MAIL_CLASS_PUBLIC, var_queue_service, 0);
+ state->dst = state->handle->stream;
+ cleanup_path = mystrdup(VSTREAM_PATH(state->dst));
+ state->queue_id = mystrdup(state->handle->id);
+ if (msg_verbose)
+ msg_info("cleanup_open: open %s", cleanup_path);
+
+ /*
+ * If there is a time to get rid of spurious log files, this is it. The
+ * down side is that this costs performance for every message, while the
+ * probability of spurious log files is quite low.
+ *
+ * XXX The defer logfile is deleted when the message is moved into the
+ * active queue. We must also remove it now, otherwise mailq produces
+ * nonsense.
+ */
+ for (cpp = log_queues; *cpp; cpp++) {
+ if (mail_queue_remove(*cpp, state->queue_id) == 0)
+ msg_warn("%s: removed spurious %s log", *cpp, state->queue_id);
+ else if (errno != ENOENT)
+ msg_fatal("%s: remove %s log: %m", *cpp, state->queue_id);
+ }
+ return (state);
+}
+
+/* cleanup_control - process client options */
+
+void cleanup_control(CLEANUP_STATE *state, int flags)
+{
+
+ /*
+ * If the client requests us to do the bouncing in case of problems,
+ * throw away the input only in case of real show-stopper errors, such as
+ * unrecognizable data (which should never happen) or insufficient space
+ * for the queue file (which will happen occasionally). Otherwise,
+ * discard input after any lethal error. See the CLEANUP_OUT_OK() macro
+ * definition.
+ */
+ if (msg_verbose)
+ msg_info("cleanup flags = %s", cleanup_strflags(flags));
+ if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) {
+ state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE;
+ } else {
+ state->err_mask = ~0;
+ }
+ if (state->flags & CLEANUP_FLAG_SMTPUTF8)
+ state->smtputf8 = SMTPUTF8_FLAG_REQUESTED;
+}
+
+/* cleanup_flush - finish queue file */
+
+int cleanup_flush(CLEANUP_STATE *state)
+{
+ int status;
+ char *junk;
+ VSTRING *trace_junk;
+
+ /*
+ * Raise these errors only if we examined all queue file records.
+ */
+ if (CLEANUP_OUT_OK(state)) {
+ if (state->recip == 0)
+ state->errs |= CLEANUP_STAT_RCPT;
+ if ((state->flags & CLEANUP_FLAG_END_SEEN) == 0)
+ state->errs |= CLEANUP_STAT_BAD;
+ }
+
+ /*
+ * Status sanitization. Always report success when the discard flag was
+ * raised by some user-specified access rule.
+ */
+ if (state->flags & CLEANUP_FLAG_DISCARD)
+ state->errs = 0;
+
+ /*
+ * Apply external mail filter.
+ *
+ * XXX Include test for a built-in action to tempfail this message.
+ */
+ if (CLEANUP_MILTER_OK(state)) {
+ if (state->milters)
+ cleanup_milter_inspect(state, state->milters);
+ else if (cleanup_milters) {
+ cleanup_milter_emul_data(state, cleanup_milters);
+ if (CLEANUP_MILTER_OK(state))
+ cleanup_milter_inspect(state, cleanup_milters);
+ }
+ }
+
+ /*
+ * Update the preliminary message size and count fields with the actual
+ * values.
+ */
+ if (CLEANUP_OUT_OK(state))
+ cleanup_final(state);
+
+ /*
+ * If there was an error that requires us to generate a bounce message
+ * (mail submitted with the Postfix sendmail command, mail forwarded by
+ * the local(8) delivery agent, or mail re-queued with "postsuper -r"),
+ * send a bounce notification, reset the error flags in case of success,
+ * and request deletion of the the incoming queue file and of the
+ * optional DSN SUCCESS records from virtual alias expansion.
+ *
+ * XXX It would make no sense to knowingly report success after we already
+ * have bounced all recipients, especially because the information in the
+ * DSN SUCCESS notice is completely redundant compared to the information
+ * in the bounce notice (however, both may be incomplete when the queue
+ * file size would exceed the safety limit).
+ *
+ * An alternative is to keep the DSN SUCCESS records and to delegate bounce
+ * notification to the queue manager, just like we already delegate
+ * success notification. This requires that we leave the undeliverable
+ * message in the incoming queue; versions up to 20050726 did exactly
+ * that. Unfortunately, this broke with over-size queue files, because
+ * the queue manager cannot handle incomplete queue files (and it should
+ * not try to do so).
+ */
+#define CAN_BOUNCE() \
+ ((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \
+ && state->sender != 0 \
+ && (state->flags & CLEANUP_FLAG_BOUNCE) != 0)
+
+ if (state->errs != 0 && CAN_BOUNCE())
+ cleanup_bounce(state);
+
+ /*
+ * Optionally, place the message on hold, but only if the message was
+ * received successfully and only if it's not being discarded for other
+ * reasons. This involves renaming the queue file before "finishing" it
+ * (or else the queue manager would grab it too early) and updating our
+ * own idea of the queue file name for error recovery and for error
+ * reporting purposes.
+ *
+ * XXX Include test for a built-in action to tempfail this message.
+ */
+ if (state->errs == 0 && (state->flags & CLEANUP_FLAG_DISCARD) == 0) {
+ if ((state->flags & CLEANUP_FLAG_HOLD) != 0
+#ifdef DELAY_ACTION
+ || state->defer_delay > 0
+#endif
+ ) {
+ myfree(state->queue_name);
+#ifdef DELAY_ACTION
+ state->queue_name = mystrdup((state->flags & CLEANUP_FLAG_HOLD) ?
+ MAIL_QUEUE_HOLD : MAIL_QUEUE_DEFERRED);
+#else
+ state->queue_name = mystrdup(MAIL_QUEUE_HOLD);
+#endif
+ mail_stream_ctl(state->handle,
+ CA_MAIL_STREAM_CTL_QUEUE(state->queue_name),
+ CA_MAIL_STREAM_CTL_CLASS((char *) 0),
+ CA_MAIL_STREAM_CTL_SERVICE((char *) 0),
+#ifdef DELAY_ACTION
+ CA_MAIL_STREAM_CTL_DELAY(state->defer_delay),
+#endif
+ CA_MAIL_STREAM_CTL_END);
+ junk = cleanup_path;
+ cleanup_path = mystrdup(VSTREAM_PATH(state->handle->stream));
+ myfree(junk);
+
+ /*
+ * XXX: When delivering to a non-incoming queue, do not consume
+ * in_flow tokens. Unfortunately we can't move the code that
+ * consumes tokens until after the mail is received, because that
+ * would increase the risk of duplicate deliveries (RFC 1047).
+ */
+ (void) mail_flow_put(1);
+ }
+ state->errs = mail_stream_finish(state->handle, (VSTRING *) 0);
+ } else {
+
+ /*
+ * XXX: When discarding mail, should we consume in_flow tokens? See
+ * also the comments above for mail that is placed on hold.
+ */
+#if 0
+ (void) mail_flow_put(1);
+#endif
+ mail_stream_cleanup(state->handle);
+ }
+ state->handle = 0;
+ state->dst = 0;
+
+ /*
+ * If there was an error, or if the message must be discarded for other
+ * reasons, remove the queue file and the optional trace file with DSN
+ * SUCCESS records from virtual alias expansion.
+ */
+ if (state->errs != 0 || (state->flags & CLEANUP_FLAG_DISCARD) != 0) {
+ if (cleanup_trace_path)
+ (void) REMOVE(vstring_str(cleanup_trace_path));
+ if (REMOVE(cleanup_path))
+ msg_warn("remove %s: %m", cleanup_path);
+ }
+
+ /*
+ * Make sure that our queue file will not be deleted by the error handler
+ * AFTER we have taken responsibility for delivery. Better to deliver
+ * twice than to lose mail.
+ */
+ trace_junk = cleanup_trace_path;
+ cleanup_trace_path = 0; /* don't delete upon error */
+ junk = cleanup_path;
+ cleanup_path = 0; /* don't delete upon error */
+
+ if (trace_junk)
+ vstring_free(trace_junk);
+ myfree(junk);
+
+ /*
+ * Cleanup internal state. This is simply complementary to the
+ * initializations at the beginning of cleanup_open().
+ */
+ if (msg_verbose)
+ msg_info("cleanup_flush: status %d", state->errs);
+ status = state->errs;
+ return (status);
+}
+
+/* cleanup_free - pay the last respects */
+
+void cleanup_free(CLEANUP_STATE *state)
+{
+
+ /*
+ * Emulate disconnect event. CLEANUP_FLAG_MILTER may be turned off after
+ * we have started.
+ */
+ if (cleanup_milters != 0 && state->milters == 0)
+ milter_disc_event(cleanup_milters);
+ cleanup_state_free(state);
+}
diff --git a/src/cleanup/cleanup_body_edit.c b/src/cleanup/cleanup_body_edit.c
new file mode 100644
index 0000000..2fff1bc
--- /dev/null
+++ b/src/cleanup/cleanup_body_edit.c
@@ -0,0 +1,236 @@
+/*++
+/* NAME
+/* cleanup_body_edit 3
+/* SUMMARY
+/* edit body content
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* int cleanup_body_edit_start(state)
+/* CLEANUP_STATE *state;
+/*
+/* int cleanup_body_edit_write(state, type, buf)
+/* CLEANUP_STATE *state;
+/* int type;
+/* VSTRING *buf;
+/*
+/* int cleanup_body_edit_finish(state)
+/* CLEANUP_STATE *state;
+/*
+/* void cleanup_body_edit_free(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* This module maintains queue file regions with body content.
+/* Regions are created on the fly, and can be reused multiple
+/* times. This module must not be called until the queue file
+/* is complete, and there must be no other read/write access
+/* to the queue file between the cleanup_body_edit_start() and
+/* cleanup_body_edit_finish() calls.
+/*
+/* cleanup_body_edit_start() performs initialization and sets
+/* the queue file write pointer to the start of the first body
+/* region.
+/*
+/* cleanup_body_edit_write() adds a queue file record to the
+/* queue file. When the current body region fills up, some
+/* unused region is reused, or a new region is created.
+/*
+/* cleanup_body_edit_finish() makes some final adjustments
+/* after the last body content record is written.
+/*
+/* cleanup_body_edit_free() frees up memory that was allocated
+/* by cleanup_body_edit_start() and cleanup_body_edit_write().
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is updated
+/* as records are processed and as errors happen.
+/* .IP type
+/* Record type.
+/* .IP buf
+/* Record content.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <rec_type.h>
+#include <record.h>
+
+/* Application-specific. */
+
+#include <cleanup.h>
+
+#define LEN(s) VSTRING_LEN(s)
+
+static int cleanup_body_edit_ptr_rec_len;
+
+/* cleanup_body_edit_start - rewrite body region pool */
+
+int cleanup_body_edit_start(CLEANUP_STATE *state)
+{
+ const char *myname = "cleanup_body_edit_start";
+ CLEANUP_REGION *curr_rp;
+
+ /*
+ * Calculate the payload size sans body.
+ */
+ state->cont_length = state->body_offset - state->data_offset;
+
+ /*
+ * One-time initialization.
+ */
+ if (state->body_regions == 0) {
+ REC_SPACE_NEED(REC_TYPE_PTR_PAYL_SIZE, cleanup_body_edit_ptr_rec_len);
+ cleanup_region_init(state);
+ }
+
+ /*
+ * Return all body regions to the free pool.
+ */
+ cleanup_region_return(state, state->body_regions);
+
+ /*
+ * Select the first region. XXX This will usually be the original body
+ * segment, but we must not count on that. Region assignments may change
+ * when header editing also uses queue file regions. XXX We don't really
+ * know if the first region will be large enough to hold the first body
+ * text record, but this problem is so rare that we will not complicate
+ * the code for it. If the first region is too small then we will simply
+ * waste it.
+ */
+ curr_rp = state->curr_body_region = state->body_regions =
+ cleanup_region_open(state, cleanup_body_edit_ptr_rec_len);
+
+ /*
+ * Link the first body region to the last message header.
+ */
+ if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (-1);
+ }
+ state->append_hdr_pt_target = curr_rp->start;
+ rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->append_hdr_pt_target);
+
+ /*
+ * Move the file write pointer to the start of the current region.
+ */
+ if (vstream_ftell(state->dst) != curr_rp->start
+ && vstream_fseek(state->dst, curr_rp->start, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (-1);
+ }
+ return (0);
+}
+
+/* cleanup_body_edit_write - add record to body region pool */
+
+int cleanup_body_edit_write(CLEANUP_STATE *state, int rec_type,
+ VSTRING *buf)
+{
+ const char *myname = "cleanup_body_edit_write";
+ CLEANUP_REGION *curr_rp = state->curr_body_region;
+ CLEANUP_REGION *next_rp;
+ off_t space_used;
+ ssize_t space_needed;
+ ssize_t rec_len;
+
+ if (msg_verbose)
+ msg_info("%s: where %ld, buflen %ld region start %ld len %ld",
+ myname, (long) curr_rp->write_offs, (long) LEN(buf),
+ (long) curr_rp->start, (long) curr_rp->len);
+
+ /*
+ * Switch to the next body region if we filled up the current one (we
+ * always append to an open-ended region). Besides space to write the
+ * specified record, we need to leave space for a final pointer record
+ * that will link this body region to the next region or to the content
+ * terminator record.
+ */
+ if (curr_rp->len > 0) {
+ space_used = curr_rp->write_offs - curr_rp->start;
+ REC_SPACE_NEED(LEN(buf), rec_len);
+ space_needed = rec_len + cleanup_body_edit_ptr_rec_len;
+ if (space_needed > curr_rp->len - space_used) {
+
+ /*
+ * Update the payload size. Connect the filled up body region to
+ * its successor.
+ */
+ state->cont_length += space_used;
+ next_rp = cleanup_region_open(state, space_needed);
+ if (msg_verbose)
+ msg_info("%s: link %ld -> %ld", myname,
+ (long) curr_rp->write_offs, (long) next_rp->start);
+ rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) next_rp->start);
+ curr_rp->write_offs = vstream_ftell(state->dst);
+ cleanup_region_close(state, curr_rp);
+ curr_rp->next = next_rp;
+
+ /*
+ * Select the new body region.
+ */
+ state->curr_body_region = curr_rp = next_rp;
+ if (vstream_fseek(state->dst, curr_rp->start, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (-1);
+ }
+ }
+ }
+
+ /*
+ * Finally, output the queue file record.
+ */
+ CLEANUP_OUT_BUF(state, rec_type, buf);
+ curr_rp->write_offs = vstream_ftell(state->dst);
+
+ return (0);
+}
+
+/* cleanup_body_edit_finish - wrap up body region pool */
+
+int cleanup_body_edit_finish(CLEANUP_STATE *state)
+{
+ CLEANUP_REGION *curr_rp = state->curr_body_region;
+
+ /*
+ * Update the payload size.
+ */
+ state->cont_length += curr_rp->write_offs - curr_rp->start;
+
+ /*
+ * Link the last body region to the content terminator record.
+ */
+ rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->xtra_offset);
+ curr_rp->write_offs = vstream_ftell(state->dst);
+ cleanup_region_close(state, curr_rp);
+
+ return (CLEANUP_OUT_OK(state) ? 0 : -1);
+}
diff --git a/src/cleanup/cleanup_bounce.c b/src/cleanup/cleanup_bounce.c
new file mode 100644
index 0000000..361875e
--- /dev/null
+++ b/src/cleanup/cleanup_bounce.c
@@ -0,0 +1,257 @@
+/*++
+/* NAME
+/* cleanup_bounce 3
+/* SUMMARY
+/* bounce all recipients
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_bounce(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* cleanup_bounce() updates the bounce log on request by client
+/* programs that cannot handle such problems themselves.
+/*
+/* Upon successful completion, all error flags are reset,
+/* and the message is scheduled for deletion.
+/* Otherwise, the CLEANUP_STAT_WRITE error flag is raised.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is
+/* updated as records are processed and as errors happen.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <stdlib.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <bounce.h>
+#include <dsn_util.h>
+#include <record.h>
+#include <rec_type.h>
+#include <dsn_mask.h>
+#include <mail_queue.h>
+#include <rec_attr_map.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR(x) vstring_str(x)
+
+/* cleanup_bounce_append - update bounce logfile */
+
+static void cleanup_bounce_append(CLEANUP_STATE *state, RECIPIENT *rcpt,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+
+ /*
+ * Don't log a spurious warning (for example, when soft_bounce is turned
+ * on). bounce_append() already logs a record when the logfile can't be
+ * updated. Set the write error flag, so that a maildrop queue file won't
+ * be destroyed.
+ */
+ if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id,
+ CLEANUP_MSG_STATS(&stats, state),
+ rcpt, "none", dsn) != 0) {
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+}
+
+/* cleanup_bounce - bounce all recipients */
+
+int cleanup_bounce(CLEANUP_STATE *state)
+{
+ const char *myname = "cleanup_bounce";
+ VSTRING *buf = vstring_alloc(100);
+ const CLEANUP_STAT_DETAIL *detail;
+ DSN_SPLIT dp;
+ const char *dsn_status;
+ const char *dsn_text;
+ char *rcpt = 0;
+ RECIPIENT recipient;
+ DSN dsn;
+ char *attr_name;
+ char *attr_value;
+ char *dsn_orcpt = 0;
+ int dsn_notify = 0;
+ char *orig_rcpt = 0;
+ char *start;
+ int rec_type;
+ int junk;
+ long curr_offset;
+ const char *encoding;
+ const char *dsn_envid;
+ int dsn_ret;
+ int bounce_err;
+
+ /*
+ * Parse the failure reason if one was given, otherwise use a generic
+ * mapping from cleanup-internal error code to (DSN + text).
+ */
+ if (state->reason) {
+ dsn_split(&dp, "5.0.0", state->reason);
+ dsn_status = DSN_STATUS(dp.dsn);
+ dsn_text = dp.text;
+ } else {
+ detail = cleanup_stat_detail(state->errs);
+ dsn_status = detail->dsn;
+ dsn_text = detail->text;
+ }
+
+ /*
+ * Create a bounce logfile with one entry for each final recipient.
+ * Degrade gracefully in case of no recipients or no queue file.
+ *
+ * Victor Duchovni observes that the number of recipients in the queue file
+ * can potentially be very large due to virtual alias expansion. This can
+ * expand the recipient count by virtual_alias_expansion_limit (default:
+ * 1000) times.
+ *
+ * After a queue file write error, purge any unwritten data (so that
+ * vstream_fseek() won't fail while trying to flush it) and reset the
+ * stream error flags to avoid false alarms.
+ */
+ if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) {
+ (void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH);
+ vstream_clearerr(state->dst);
+ }
+ if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
+ msg_fatal("%s: seek %s: %m", myname, cleanup_path);
+
+ while ((state->errs & CLEANUP_STAT_WRITE) == 0) {
+ if ((curr_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+ if ((rec_type = rec_get(state->dst, buf, 0)) <= 0
+ || rec_type == REC_TYPE_END)
+ break;
+ start = STR(buf);
+ if (rec_type == REC_TYPE_ATTR) {
+ if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
+ || *attr_value == 0)
+ continue;
+ /* Map attribute names to pseudo record type. */
+ if ((junk = rec_attr_map(attr_name)) != 0) {
+ start = attr_value;
+ rec_type = junk;
+ }
+ }
+ switch (rec_type) {
+ case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */
+ if (dsn_orcpt != 0) /* can't happen */
+ myfree(dsn_orcpt);
+ dsn_orcpt = mystrdup(start);
+ break;
+ case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */
+ if (alldig(start) && (junk = atoi(start)) > 0
+ && DSN_NOTIFY_OK(junk))
+ dsn_notify = junk;
+ else
+ dsn_notify = 0;
+ break;
+ case REC_TYPE_ORCP: /* unmodified RCPT TO address */
+ if (orig_rcpt != 0) /* can't happen */
+ myfree(orig_rcpt);
+ orig_rcpt = mystrdup(start);
+ break;
+ case REC_TYPE_RCPT: /* rewritten RCPT TO address */
+ rcpt = start;
+ RECIPIENT_ASSIGN(&recipient, curr_offset,
+ dsn_orcpt ? dsn_orcpt : "", dsn_notify,
+ orig_rcpt ? orig_rcpt : rcpt, rcpt);
+ (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
+ cleanup_bounce_append(state, &recipient, &dsn);
+ /* FALLTHROUGH */
+ case REC_TYPE_DRCP: /* canceled recipient */
+ case REC_TYPE_DONE: /* can't happen */
+ if (orig_rcpt != 0) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_orcpt != 0) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ dsn_notify = 0;
+ break;
+ }
+ }
+ if (orig_rcpt != 0) /* can't happen */
+ myfree(orig_rcpt);
+ if (dsn_orcpt != 0) /* can't happen */
+ myfree(dsn_orcpt);
+
+ /*
+ * No recipients. Yes, this can happen.
+ */
+ if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) {
+ RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown");
+ (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text);
+ cleanup_bounce_append(state, &recipient, &dsn);
+ }
+ vstring_free(buf);
+
+ /*
+ * Flush the bounce logfile to the sender. See also qmgr_active.c.
+ */
+ if ((state->errs & CLEANUP_STAT_WRITE) == 0) {
+ if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0)
+ encoding = MAIL_ATTR_ENC_NONE;
+ dsn_envid = state->dsn_envid ?
+ state->dsn_envid : "";
+ /* Do not send unfiltered (body) content. */
+ dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ?
+ DSN_RET_HDRS : state->dsn_ret;
+
+ if (state->verp_delims == 0 || var_verp_bounce_off) {
+ bounce_err =
+ bounce_flush(BOUNCE_FLAG_CLEAN,
+ state->queue_name, state->queue_id,
+ encoding, state->smtputf8, state->sender,
+ dsn_envid, dsn_ret);
+ } else {
+ bounce_err =
+ bounce_flush_verp(BOUNCE_FLAG_CLEAN,
+ state->queue_name, state->queue_id,
+ encoding, state->smtputf8, state->sender,
+ dsn_envid, dsn_ret, state->verp_delims);
+ }
+ if (bounce_err != 0) {
+ msg_warn("%s: bounce message failure", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+
+ /*
+ * Schedule this message (and trace logfile) for deletion when all is
+ * well. When all is not well these files would be deleted too, but the
+ * client would get a different completion status so we have to carefully
+ * maintain the bits anyway.
+ */
+ if ((state->errs &= CLEANUP_STAT_WRITE) == 0)
+ state->flags |= CLEANUP_FLAG_DISCARD;
+
+ return (state->errs);
+}
diff --git a/src/cleanup/cleanup_envelope.c b/src/cleanup/cleanup_envelope.c
new file mode 100644
index 0000000..a4b991d
--- /dev/null
+++ b/src/cleanup/cleanup_envelope.c
@@ -0,0 +1,502 @@
+/*++
+/* NAME
+/* cleanup_envelope 3
+/* SUMMARY
+/* process envelope segment
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* void cleanup_envelope(state, type, buf, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *buf;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module processes envelope records and writes the result
+/* to the queue file. It validates the message structure, rewrites
+/* sender/recipient addresses to canonical form, and expands recipients
+/* according to entries in the virtual table. This routine absorbs but
+/* does not emit the envelope to content boundary record.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is updated
+/* as records are processed and as errors happen.
+/* .IP type
+/* Record type.
+/* .IP buf
+/* Record content.
+/* .IP len
+/* Record content length.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h> /* ssscanf() */
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <nvtable.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <cleanup_user.h>
+#include <qmgr_user.h>
+#include <mail_params.h>
+#include <verp_sender.h>
+#include <mail_proto.h>
+#include <dsn_mask.h>
+#include <rec_attr_map.h>
+#include <smtputf8.h>
+#include <deliver_request.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+static void cleanup_envelope_process(CLEANUP_STATE *, int, const char *, ssize_t);
+
+/* cleanup_envelope - initialize message envelope */
+
+void cleanup_envelope(CLEANUP_STATE *state, int type,
+ const char *str, ssize_t len)
+{
+
+ /*
+ * The message size and count record goes first, so it can easily be
+ * updated in place. This information takes precedence over any size
+ * estimate provided by the client. It's all in one record, data size
+ * first, for backwards compatibility reasons.
+ */
+ cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
+ (REC_TYPE_SIZE_CAST1) 0, /* extra offs - content offs */
+ (REC_TYPE_SIZE_CAST2) 0, /* content offset */
+ (REC_TYPE_SIZE_CAST3) 0, /* recipient count */
+ (REC_TYPE_SIZE_CAST4) 0, /* qmgr options */
+ (REC_TYPE_SIZE_CAST5) 0, /* content length */
+ (REC_TYPE_SIZE_CAST6) 0); /* smtputf8 */
+
+ /*
+ * Pass control to the actual envelope processing routine.
+ */
+ state->action = cleanup_envelope_process;
+ cleanup_envelope_process(state, type, str, len);
+}
+
+/* cleanup_envelope_process - process one envelope record */
+
+static void cleanup_envelope_process(CLEANUP_STATE *state, int type,
+ const char *buf, ssize_t len)
+{
+ const char *myname = "cleanup_envelope_process";
+ char *attr_name;
+ char *attr_value;
+ const char *error_text;
+ int extra_opts;
+ int junk;
+ int mapped_type = type;
+ const char *mapped_buf = buf;
+ int milter_count;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ if (msg_verbose)
+ msg_info("initial envelope %c %.*s", type, (int) len, buf);
+
+ if (type == REC_TYPE_FLGS) {
+ /* Not part of queue file format. */
+ extra_opts = atoi(buf);
+ if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA)
+ msg_warn("%s: ignoring bad extra flags: 0x%x",
+ state->queue_id, extra_opts);
+ else
+ state->flags |= extra_opts;
+ return;
+ }
+#ifdef DELAY_ACTION
+ if (type == REC_TYPE_DELAY) {
+ /* Not part of queue file format. */
+ defer_delay = atoi(buf);
+ if (defer_delay <= 0)
+ msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf);
+ else
+ state->defer_delay = defer_delay;
+ return;
+ }
+#endif
+
+ /*
+ * XXX We instantiate a MILTERS structure even when the filter count is
+ * zero (for example, all filters are in ACCEPT state, or the SMTP server
+ * sends a dummy MILTERS structure without any filters), otherwise the
+ * cleanup server would apply the non_smtpd_milters setting
+ * inappropriately.
+ */
+ if (type == REC_TYPE_MILT_COUNT) {
+ /* Not part of queue file format. */
+ if ((milter_count = atoi(buf)) >= 0)
+ cleanup_milter_receive(state, milter_count);
+ return;
+ }
+
+ /*
+ * Map DSN attribute name to pseudo record type so that we don't have to
+ * pollute the queue file with records that are incompatible with past
+ * Postfix versions. Preferably, people should be able to back out from
+ * an upgrade without losing mail.
+ */
+ if (type == REC_TYPE_ATTR) {
+ vstring_strcpy(state->attr_buf, buf);
+ error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value);
+ if (error_text != 0) {
+ msg_warn("%s: message rejected: malformed attribute: %s: %.100s",
+ state->queue_id, error_text, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ /* Zero-length values are place holders for unavailable values. */
+ if (*attr_value == 0) {
+ msg_warn("%s: spurious null attribute value for \"%s\" -- ignored",
+ state->queue_id, attr_name);
+ return;
+ }
+ if ((junk = rec_attr_map(attr_name)) != 0) {
+ mapped_buf = attr_value;
+ mapped_type = junk;
+ }
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (strchr(REC_TYPE_ENVELOPE, type) == 0) {
+ msg_warn("%s: message rejected: unexpected record type %d in envelope",
+ state->queue_id, type);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * Although recipient records appear at the end of the initial or
+ * extracted envelope, the code for processing recipient records is first
+ * because there can be lots of them.
+ *
+ * Recipient records may be mixed with other information (such as FILTER or
+ * REDIRECT actions from SMTPD). In that case the queue manager needs to
+ * examine all queue file records before it can start delivery. This is
+ * not a problem when SMTPD recipient lists are small.
+ *
+ * However, if recipient records are not mixed with other records
+ * (typically, mailing list mail) then we can make an optimization: the
+ * queue manager does not need to examine every envelope record before it
+ * can start deliveries. This can help with very large mailing lists.
+ */
+
+ /*
+ * On the transition from non-recipient records to recipient records,
+ * emit some records and do some sanity checks.
+ *
+ * XXX Moving the envelope sender (and the test for its presence) to the
+ * extracted segment can reduce qmqpd memory requirements because it no
+ * longer needs to read the entire message into main memory.
+ */
+ if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
+ && strchr(REC_TYPE_ENV_RECIPIENT, type) != 0) {
+ if (state->sender == 0) {
+ msg_warn("%s: message rejected: missing sender envelope record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->arrival_time.tv_sec == 0) {
+ msg_warn("%s: message rejected: missing time envelope record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * XXX This works by accident, because the sender is recorded at the
+ * beginning of the envelope segment.
+ */
+ if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0
+ && state->sender && *state->sender
+ && var_delay_warn_time > 0) {
+ cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT,
+ REC_TYPE_WARN_ARG(state->arrival_time.tv_sec
+ + var_delay_warn_time));
+ }
+ state->flags |= CLEANUP_FLAG_INRCPT;
+ }
+
+ /*
+ * Initial envelope recipient record processing.
+ */
+ if (type == REC_TYPE_RCPT) {
+ if (state->sender == 0) { /* protect showq */
+ msg_warn("%s: message rejected: envelope recipient precedes sender",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->orig_rcpt == 0)
+ state->orig_rcpt = mystrdup(buf);
+ cleanup_addr_recipient(state, buf);
+ if (cleanup_milters != 0
+ && state->milters == 0
+ && CLEANUP_MILTER_OK(state))
+ cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip);
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) {
+ if (state->orig_rcpt != 0) {
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ }
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_ORCPT) {
+ if (state->dsn_orcpt) {
+ msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>",
+ state->queue_id, state->dsn_orcpt);
+ myfree(state->dsn_orcpt);
+ }
+ state->dsn_orcpt = mystrdup(mapped_buf);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_NOTIFY) {
+ if (state->dsn_notify) {
+ msg_warn("%s: ignoring out-of-order DSN notify record <%d>",
+ state->queue_id, state->dsn_notify);
+ state->dsn_notify = 0;
+ }
+ if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
+ || DSN_NOTIFY_OK(junk) == 0)
+ msg_warn("%s: ignoring malformed DSN notify record <%.200s>",
+ state->queue_id, buf);
+ else
+ state->qmgr_opts |=
+ QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk);
+ return;
+ }
+ if (type == REC_TYPE_ORCP) {
+ if (state->orig_rcpt != 0) {
+ msg_warn("%s: ignoring out-of-order original recipient record <%.200s>",
+ state->queue_id, state->orig_rcpt);
+ myfree(state->orig_rcpt);
+ }
+ state->orig_rcpt = mystrdup(buf);
+ return;
+ }
+ if (type == REC_TYPE_MESG) {
+ state->action = cleanup_message;
+ if (state->flags & CLEANUP_FLAG_INRCPT) {
+ if (state->milters || cleanup_milters) {
+ /* Make room to append recipient. */
+ if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ state->flags &= ~CLEANUP_FLAG_INRCPT;
+ }
+ return;
+ }
+
+ /*
+ * Initial envelope non-recipient record processing.
+ *
+ * If the message was requeued with "postsuper -r" use their
+ * SMTPUTF8_REQUESTED flag.
+ */
+ if (state->flags & CLEANUP_FLAG_INRCPT)
+ /* Tell qmgr that recipient records are mixed with other information. */
+ state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER;
+ if (type == REC_TYPE_SIZE) {
+ /* Use our own SIZE record, except for the SMTPUTF8_REQUESTED flag. */
+ (void) sscanf(buf, "%*s $*s %*s %*s %*s %d", &state->smtputf8);
+ state->smtputf8 &= SMTPUTF8_FLAG_REQUESTED;
+ return;
+ }
+ if (mapped_type == REC_TYPE_CTIME)
+ /* Use our own expiration time base record instead. */
+ return;
+ if (type == REC_TYPE_TIME) {
+ /* First instance wins. */
+ if (state->arrival_time.tv_sec == 0) {
+ REC_TYPE_TIME_SCAN(buf, state->arrival_time);
+ cleanup_out(state, type, buf, len);
+ }
+ /* Generate our own expiration time base record. */
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%ld",
+ MAIL_ATTR_CREATE_TIME, (long) time((time_t *) 0));
+ return;
+ }
+ if (type == REC_TYPE_FULL) {
+ /* First instance wins. */
+ if (state->fullname == 0) {
+ state->fullname = mystrdup(buf);
+ cleanup_out(state, type, buf, len);
+ }
+ return;
+ }
+ if (type == REC_TYPE_FROM) {
+ off_t after_sender_offs;
+
+ /* Allow only one instance. */
+ if (state->sender != 0) {
+ msg_warn("%s: message rejected: multiple envelope sender records",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->milters || cleanup_milters) {
+ /* Remember the sender record offset. */
+ if ((state->sender_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ after_sender_offs = cleanup_addr_sender(state, buf);
+ if (state->milters || cleanup_milters) {
+ /* Remember the after-sender record offset. */
+ state->sender_pt_target = after_sender_offs;
+ }
+ if (cleanup_milters != 0
+ && state->milters == 0
+ && CLEANUP_MILTER_OK(state))
+ cleanup_milter_emul_mail(state, cleanup_milters, state->sender);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_ENVID) {
+ /* Don't break "postsuper -r" after Milter overrides ENVID. */
+ if (!allprint(mapped_buf)) {
+ msg_warn("%s: message rejected: bad DSN envelope ID record",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->dsn_envid != 0)
+ myfree(state->dsn_envid);
+ state->dsn_envid = mystrdup(mapped_buf);
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+ if (mapped_type == REC_TYPE_DSN_RET) {
+ /* Don't break "postsuper -r" after Milter overrides RET. */
+ if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0
+ || DSN_RET_OK(junk) == 0) {
+ msg_warn("%s: message rejected: bad DSN RET record <%.200s>",
+ state->queue_id, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ state->dsn_ret = junk;
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+ if (type == REC_TYPE_WARN) {
+ /* First instance wins. */
+ if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0) {
+ state->flags |= CLEANUP_FLAG_WARN_SEEN;
+ cleanup_out(state, type, buf, len);
+ }
+ return;
+ }
+ /* XXX Needed for cleanup_bounce(); sanity check usage. */
+ if (type == REC_TYPE_VERP) {
+ if (state->verp_delims == 0) {
+ if (state->sender == 0 || state->sender[0] == 0) {
+ msg_warn("%s: ignoring VERP request for null sender",
+ state->queue_id);
+ } else if (verp_delims_verify(buf) != 0) {
+ msg_warn("%s: ignoring bad VERP request: \"%.100s\"",
+ state->queue_id, buf);
+ } else {
+ state->verp_delims = mystrdup(buf);
+ cleanup_out(state, type, buf, len);
+ }
+ }
+ return;
+ }
+ if (type == REC_TYPE_ATTR) {
+ if (state->attr->used >= var_qattr_count_limit) {
+ msg_warn("%s: message rejected: attribute count exceeds limit %d",
+ state->queue_id, var_qattr_count_limit);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (strcmp(attr_name, MAIL_ATTR_RWR_CONTEXT) == 0) {
+ /* Choose header rewriting context. See also cleanup_addr.c. */
+ if (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)) {
+ state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ } else if (STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)) {
+ state->hdr_rewrite_context =
+ (*var_remote_rwr_domain ? MAIL_ATTR_RWR_REMOTE : 0);
+ } else {
+ msg_warn("%s: message rejected: bad rewriting context: %.100s",
+ state->queue_id, attr_value);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ }
+ if (strcmp(attr_name, MAIL_ATTR_TRACE_FLAGS) == 0) {
+ if (!alldig(attr_value)) {
+ msg_warn("%s: message rejected: bad TFLAG record <%.200s>",
+ state->queue_id, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->tflags == 0)
+ state->tflags = DEL_REQ_TRACE_FLAGS(atoi(attr_value));
+ }
+ nvtable_update(state->attr, attr_name, attr_value);
+ cleanup_out(state, type, buf, len);
+ return;
+ } else {
+ cleanup_out(state, type, buf, len);
+ return;
+ }
+}
diff --git a/src/cleanup/cleanup_extracted.c b/src/cleanup/cleanup_extracted.c
new file mode 100644
index 0000000..e6c2122
--- /dev/null
+++ b/src/cleanup/cleanup_extracted.c
@@ -0,0 +1,328 @@
+/*++
+/* NAME
+/* cleanup_extracted 3
+/* SUMMARY
+/* process extracted segment
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_extracted(state, type, buf, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *buf;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module processes message records with information extracted
+/* from message content, or with recipients that are stored after the
+/* message content. It updates recipient records, writes extracted
+/* information records to the output, and writes the queue
+/* file end marker. The queue file is left in a state that
+/* is suitable for Milter inspection, but the size record still
+/* contains dummy values.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is updated
+/* as records are processed and as errors happen.
+/* .IP type
+/* Record type.
+/* .IP buf
+/* Record content.
+/* .IP len
+/* Record content length.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <nvtable.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <qmgr_user.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <dsn_mask.h>
+#include <rec_attr_map.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR(x) vstring_str(x)
+
+static void cleanup_extracted_process(CLEANUP_STATE *, int, const char *, ssize_t);
+static void cleanup_extracted_finish(CLEANUP_STATE *);
+
+/* cleanup_extracted - initialize extracted segment */
+
+void cleanup_extracted(CLEANUP_STATE *state, int type,
+ const char *buf, ssize_t len)
+{
+
+ /*
+ * Start the extracted segment.
+ */
+ cleanup_out_string(state, REC_TYPE_XTRA, "");
+
+ /*
+ * Pass control to the actual envelope processing routine.
+ */
+ state->action = cleanup_extracted_process;
+ cleanup_extracted_process(state, type, buf, len);
+}
+
+/* cleanup_extracted_process - process one extracted envelope record */
+
+void cleanup_extracted_process(CLEANUP_STATE *state, int type,
+ const char *buf, ssize_t len)
+{
+ const char *myname = "cleanup_extracted_process";
+ const char *encoding;
+ char *attr_name;
+ char *attr_value;
+ const char *error_text;
+ int extra_opts;
+ int junk;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ if (msg_verbose)
+ msg_info("extracted envelope %c %.*s", type, (int) len, buf);
+
+ if (type == REC_TYPE_FLGS) {
+ /* Not part of queue file format. */
+ extra_opts = atoi(buf);
+ if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA)
+ msg_warn("%s: ignoring bad extra flags: 0x%x",
+ state->queue_id, extra_opts);
+ else
+ state->flags |= extra_opts;
+ return;
+ }
+#ifdef DELAY_ACTION
+ if (type == REC_TYPE_DELAY) {
+ /* Not part of queue file format. */
+ defer_delay = atoi(buf);
+ if (defer_delay <= 0)
+ msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf);
+ else
+ state->defer_delay = defer_delay;
+ return;
+ }
+#endif
+
+ if (strchr(REC_TYPE_EXTRACT, type) == 0) {
+ msg_warn("%s: message rejected: "
+ "unexpected record type %d in extracted envelope",
+ state->queue_id, type);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * Map DSN attribute name to pseudo record type so that we don't have to
+ * pollute the queue file with records that are incompatible with past
+ * Postfix versions. Preferably, people should be able to back out from
+ * an upgrade without losing mail.
+ */
+ if (type == REC_TYPE_ATTR) {
+ vstring_strcpy(state->attr_buf, buf);
+ error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value);
+ if (error_text != 0) {
+ msg_warn("%s: message rejected: malformed attribute: %s: %.100s",
+ state->queue_id, error_text, buf);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ /* Zero-length values are place holders for unavailable values. */
+ if (*attr_value == 0) {
+ msg_warn("%s: spurious null attribute value for \"%s\" -- ignored",
+ state->queue_id, attr_name);
+ return;
+ }
+ if ((junk = rec_attr_map(attr_name)) != 0) {
+ buf = attr_value;
+ type = junk;
+ }
+ }
+
+ /*
+ * On the transition from non-recipient records to recipient records,
+ * emit optional information from header/body content.
+ */
+ if ((state->flags & CLEANUP_FLAG_INRCPT) == 0
+ && strchr(REC_TYPE_EXT_RECIPIENT, type) != 0) {
+ if (state->filter != 0)
+ cleanup_out_string(state, REC_TYPE_FILT, state->filter);
+ if (state->redirect != 0)
+ cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
+ if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) != 0)
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ENCODING, encoding);
+ state->flags |= CLEANUP_FLAG_INRCPT;
+ /* Make room to append more meta records. */
+ if (state->milters || cleanup_milters) {
+ if ((state->append_meta_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ if ((state->append_meta_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ }
+
+ /*
+ * Extracted envelope recipient record processing.
+ */
+ if (type == REC_TYPE_RCPT) {
+ if (state->sender == 0) { /* protect showq */
+ msg_warn("%s: message rejected: envelope recipient precedes sender",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+ if (state->orig_rcpt == 0)
+ state->orig_rcpt = mystrdup(buf);
+ cleanup_addr_recipient(state, buf);
+ if (cleanup_milters != 0
+ && state->milters == 0
+ && CLEANUP_MILTER_OK(state))
+ cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip);
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) {
+ if (state->orig_rcpt != 0) {
+ myfree(state->orig_rcpt);
+ state->orig_rcpt = 0;
+ }
+ if (state->dsn_orcpt != 0) {
+ myfree(state->dsn_orcpt);
+ state->dsn_orcpt = 0;
+ }
+ state->dsn_notify = 0;
+ return;
+ }
+ if (type == REC_TYPE_DSN_ORCPT) {
+ if (state->dsn_orcpt) {
+ msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>",
+ state->queue_id, state->dsn_orcpt);
+ myfree(state->dsn_orcpt);
+ }
+ state->dsn_orcpt = mystrdup(buf);
+ return;
+ }
+ if (type == REC_TYPE_DSN_NOTIFY) {
+ if (state->dsn_notify) {
+ msg_warn("%s: ignoring out-of-order DSN notify record <%d>",
+ state->queue_id, state->dsn_notify);
+ state->dsn_notify = 0;
+ }
+ if (!alldig(buf) || (junk = atoi(buf)) == 0 || DSN_NOTIFY_OK(junk) == 0)
+ msg_warn("%s: ignoring malformed dsn notify record <%.200s>",
+ state->queue_id, buf);
+ else
+ state->qmgr_opts |=
+ QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk);
+ return;
+ }
+ if (type == REC_TYPE_ORCP) {
+ if (state->orig_rcpt != 0) {
+ msg_warn("%s: ignoring out-of-order original recipient record <%.200s>",
+ state->queue_id, buf);
+ myfree(state->orig_rcpt);
+ }
+ state->orig_rcpt = mystrdup(buf);
+ return;
+ }
+ if (type == REC_TYPE_END) {
+ /* Make room to append recipient. */
+ if ((state->milters || cleanup_milters)
+ && state->append_rcpt_pt_offset < 0) {
+ if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path);
+ }
+ state->flags &= ~CLEANUP_FLAG_INRCPT;
+ state->flags |= CLEANUP_FLAG_END_SEEN;
+ cleanup_extracted_finish(state);
+ return;
+ }
+
+ /*
+ * Extracted envelope non-recipient record processing.
+ */
+ if (state->flags & CLEANUP_FLAG_INRCPT)
+ /* Tell qmgr that recipient records are mixed with other information. */
+ state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER;
+ cleanup_out(state, type, buf, len);
+ return;
+}
+
+/* cleanup_extracted_finish - complete the third message segment */
+
+void cleanup_extracted_finish(CLEANUP_STATE *state)
+{
+
+ /*
+ * On the way out, add the optional automatic BCC recipient.
+ */
+ if ((state->flags & CLEANUP_FLAG_BCC_OK)
+ && state->recip != 0 && *var_always_bcc)
+ cleanup_addr_bcc(state, var_always_bcc);
+
+ /*
+ * Flush non-Milter header/body_checks BCC recipients. Clear hbc_rcpt
+ * so that it can be used for other purposes.
+ */
+ if (state->hbc_rcpt) {
+ if (CLEANUP_OUT_OK(state) && state->recip != 0) {
+ char **cpp;
+
+ for (cpp = state->hbc_rcpt->argv; *cpp; cpp++)
+ cleanup_addr_bcc(state, *cpp);
+ }
+ argv_free(state->hbc_rcpt);
+ state->hbc_rcpt = 0;
+ }
+
+ /*
+ * Terminate the extracted segment.
+ */
+ cleanup_out_string(state, REC_TYPE_END, "");
+}
diff --git a/src/cleanup/cleanup_final.c b/src/cleanup/cleanup_final.c
new file mode 100644
index 0000000..db77fc7
--- /dev/null
+++ b/src/cleanup/cleanup_final.c
@@ -0,0 +1,78 @@
+/*++
+/* NAME
+/* cleanup_final 3
+/* SUMMARY
+/* finalize queue file
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_final(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* cleanup_final() performs final queue file content (not
+/* attribute) updates so that the file is ready to be closed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <rec_type.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_final - final queue file content updates */
+
+void cleanup_final(CLEANUP_STATE *state)
+{
+ const char *myname = "cleanup_final";
+
+ /*
+ * vstream_fseek() would flush the buffer anyway, but the code just reads
+ * better if we flush first, because it makes seek error handling more
+ * straightforward.
+ */
+ if (vstream_fflush(state->dst)) {
+ if (errno == EFBIG) {
+ msg_warn("%s: queue file size limit exceeded", state->queue_id);
+ state->errs |= CLEANUP_STAT_SIZE;
+ } else {
+ msg_warn("%s: write queue file: %m", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ return;
+ }
+
+ /*
+ * Update the preliminary message size and count fields with the actual
+ * values.
+ */
+ if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0)
+ msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path);
+ cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT,
+ (REC_TYPE_SIZE_CAST1) (state->xtra_offset - state->data_offset),
+ (REC_TYPE_SIZE_CAST2) state->data_offset,
+ (REC_TYPE_SIZE_CAST3) state->rcpt_count,
+ (REC_TYPE_SIZE_CAST4) state->qmgr_opts,
+ (REC_TYPE_SIZE_CAST5) state->cont_length,
+ (REC_TYPE_SIZE_CAST6) state->smtputf8);
+}
diff --git a/src/cleanup/cleanup_init.c b/src/cleanup/cleanup_init.c
new file mode 100644
index 0000000..5719f0a
--- /dev/null
+++ b/src/cleanup/cleanup_init.c
@@ -0,0 +1,483 @@
+/*++
+/* NAME
+/* cleanup_init 3
+/* SUMMARY
+/* cleanup callable interface, initializations
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* CONFIG_INT_TABLE cleanup_int_table[];
+/*
+/* CONFIG_BOOL_TABLE cleanup_bool_table[];
+/*
+/* CONFIG_STR_TABLE cleanup_str_table[];
+/*
+/* CONFIG_TIME_TABLE cleanup_time_table[];
+/*
+/* void cleanup_pre_jail(service_name, argv)
+/* char *service_name;
+/* char **argv;
+/*
+/* void cleanup_post_jail(service_name, argv)
+/* char *service_name;
+/* char **argv;
+/*
+/* char *cleanup_path;
+/* VSTRING *cleanup_trace_path;
+/*
+/* void cleanup_all()
+/*
+/* void cleanup_sig(sigval)
+/* int sigval;
+/* DESCRIPTION
+/* This module implements a callable interface to the cleanup service
+/* for one-time initializations that must be done before any message
+/* processing can take place.
+/*
+/* cleanup_{int,str,time}_table[] specify configuration
+/* parameters that must be initialized before calling any functions
+/* in this module. These tables satisfy the interface as specified in
+/* single_service(3).
+/*
+/* cleanup_pre_jail() and cleanup_post_jail() perform mandatory
+/* initializations before and after the process enters the optional
+/* chroot jail. These functions satisfy the interface as specified
+/* in single_service(3).
+/*
+/* cleanup_path is either a null pointer or it is the name of a queue
+/* file that currently is being written. This information is used
+/* by cleanup_all() to remove incomplete files after a fatal error,
+/* or by cleanup_sig() after arrival of a SIGTERM signal.
+/*
+/* cleanup_trace_path is either a null pointer or the pathname of a
+/* trace logfile with DSN SUCCESS notifications. This information is
+/* used to remove a trace file when the mail transaction is canceled.
+/*
+/* cleanup_all() must be called in case of fatal error, in order
+/* to remove an incomplete queue file.
+/*
+/* cleanup_sig() must be called in case of SIGTERM, in order
+/* to remove an incomplete queue file.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* cleanup_api(3) cleanup callable interface, message processing
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <signal.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <name_code.h>
+#include <name_mask.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_addr.h>
+#include <mail_params.h>
+#include <mail_version.h> /* milter_macro_v */
+#include <ext_prop.h>
+#include <flush_clnt.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+ /*
+ * Global state: any queue files that we have open, so that the error
+ * handler can clean up in case of trouble.
+ */
+char *cleanup_path; /* queue file name */
+
+ /*
+ * Another piece of global state: pathnames of partial bounce or trace
+ * logfiles that need to be cleaned up when the cleanup request is aborted.
+ */
+VSTRING *cleanup_trace_path;
+
+ /*
+ * Tunable parameters.
+ */
+int var_hopcount_limit; /* max mailer hop count */
+char *var_canonical_maps; /* common canonical maps */
+char *var_send_canon_maps; /* sender canonical maps */
+char *var_rcpt_canon_maps; /* recipient canonical maps */
+char *var_canon_classes; /* what to canonicalize */
+char *var_send_canon_classes; /* what sender to canonicalize */
+char *var_rcpt_canon_classes; /* what recipient to canonicalize */
+char *var_virt_alias_maps; /* virtual alias maps */
+char *var_masq_domains; /* masquerade domains */
+char *var_masq_exceptions; /* users not masqueraded */
+char *var_header_checks; /* primary header checks */
+char *var_mimehdr_checks; /* mime header checks */
+char *var_nesthdr_checks; /* nested header checks */
+char *var_body_checks; /* any body checks */
+int var_dup_filter_limit; /* recipient dup filter */
+char *var_empty_addr; /* destination of bounced bounces */
+int var_delay_warn_time; /* delay that triggers warning */
+char *var_prop_extension; /* propagate unmatched extension */
+char *var_always_bcc; /* big brother */
+char *var_rcpt_witheld; /* recipients not disclosed */
+char *var_masq_classes; /* what to masquerade */
+int var_qattr_count_limit; /* named attribute limit */
+int var_virt_recur_limit; /* maximum virtual alias recursion */
+int var_virt_expan_limit; /* maximum virtual alias expansion */
+int var_body_check_len; /* when to stop body scan */
+char *var_send_bcc_maps; /* sender auto-bcc maps */
+char *var_rcpt_bcc_maps; /* recipient auto-bcc maps */
+char *var_remote_rwr_domain; /* header-only surrogate */
+char *var_msg_reject_chars; /* reject these characters */
+char *var_msg_strip_chars; /* strip these characters */
+int var_verp_bounce_off; /* don't verp the bounces */
+int var_milt_conn_time; /* milter connect/handshake timeout */
+int var_milt_cmd_time; /* milter command timeout */
+int var_milt_msg_time; /* milter content timeout */
+char *var_milt_protocol; /* Sendmail 8 milter protocol */
+char *var_milt_def_action; /* default milter action */
+char *var_milt_daemon_name; /* {daemon_name} macro value */
+char *var_milt_v; /* {v} macro value */
+char *var_milt_conn_macros; /* connect macros */
+char *var_milt_helo_macros; /* HELO macros */
+char *var_milt_mail_macros; /* MAIL FROM macros */
+char *var_milt_rcpt_macros; /* RCPT TO macros */
+char *var_milt_data_macros; /* DATA macros */
+char *var_milt_eoh_macros; /* end-of-header macros */
+char *var_milt_eod_macros; /* end-of-data macros */
+char *var_milt_unk_macros; /* unknown command macros */
+char *var_cleanup_milters; /* non-SMTP mail */
+char *var_milt_head_checks; /* post-Milter header checks */
+char *var_milt_macro_deflts; /* default macro settings */
+int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */
+int var_always_add_hdrs; /* always add missing headers */
+int var_virt_addrlen_limit; /* stop exponential growth */
+char *var_hfrom_format; /* header_from_format */
+int var_cleanup_mask_stray_cr_lf; /* replace stray CR or LF with space */
+
+const CONFIG_INT_TABLE cleanup_int_table[] = {
+ VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0,
+ VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
+ VAR_QATTR_COUNT_LIMIT, DEF_QATTR_COUNT_LIMIT, &var_qattr_count_limit, 1, 0,
+ VAR_VIRT_RECUR_LIMIT, DEF_VIRT_RECUR_LIMIT, &var_virt_recur_limit, 1, 0,
+ VAR_VIRT_EXPAN_LIMIT, DEF_VIRT_EXPAN_LIMIT, &var_virt_expan_limit, 1, 0,
+ VAR_VIRT_ADDRLEN_LIMIT, DEF_VIRT_ADDRLEN_LIMIT, &var_virt_addrlen_limit, 1, 0,
+ VAR_BODY_CHECK_LEN, DEF_BODY_CHECK_LEN, &var_body_check_len, 0, 0,
+ 0,
+};
+
+const CONFIG_BOOL_TABLE cleanup_bool_table[] = {
+ VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off,
+ VAR_AUTO_8BIT_ENC_HDR, DEF_AUTO_8BIT_ENC_HDR, &var_auto_8bit_enc_hdr,
+ VAR_ALWAYS_ADD_HDRS, DEF_ALWAYS_ADD_HDRS, &var_always_add_hdrs,
+ VAR_CLEANUP_MASK_STRAY_CR_LF, DEF_CLEANUP_MASK_STRAY_CR_LF, &var_cleanup_mask_stray_cr_lf,
+ 0,
+};
+
+const CONFIG_TIME_TABLE cleanup_time_table[] = {
+ VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0,
+ VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, &var_milt_conn_time, 1, 0,
+ VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, &var_milt_cmd_time, 1, 0,
+ VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, &var_milt_msg_time, 1, 0,
+ 0,
+};
+
+const CONFIG_STR_TABLE cleanup_str_table[] = {
+ VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
+ VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
+ VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
+ VAR_CANON_CLASSES, DEF_CANON_CLASSES, &var_canon_classes, 1, 0,
+ VAR_SEND_CANON_CLASSES, DEF_SEND_CANON_CLASSES, &var_send_canon_classes, 1, 0,
+ VAR_RCPT_CANON_CLASSES, DEF_RCPT_CANON_CLASSES, &var_rcpt_canon_classes, 1, 0,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_MASQ_DOMAINS, DEF_MASQ_DOMAINS, &var_masq_domains, 0, 0,
+ VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
+ VAR_MASQ_EXCEPTIONS, DEF_MASQ_EXCEPTIONS, &var_masq_exceptions, 0, 0,
+ VAR_HEADER_CHECKS, DEF_HEADER_CHECKS, &var_header_checks, 0, 0,
+ VAR_MIMEHDR_CHECKS, DEF_MIMEHDR_CHECKS, &var_mimehdr_checks, 0, 0,
+ VAR_NESTHDR_CHECKS, DEF_NESTHDR_CHECKS, &var_nesthdr_checks, 0, 0,
+ VAR_BODY_CHECKS, DEF_BODY_CHECKS, &var_body_checks, 0, 0,
+ VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0,
+ VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0,
+ VAR_RCPT_WITHELD, DEF_RCPT_WITHELD, &var_rcpt_witheld, 0, 0,
+ VAR_MASQ_CLASSES, DEF_MASQ_CLASSES, &var_masq_classes, 0, 0,
+ VAR_SEND_BCC_MAPS, DEF_SEND_BCC_MAPS, &var_send_bcc_maps, 0, 0,
+ VAR_RCPT_BCC_MAPS, DEF_RCPT_BCC_MAPS, &var_rcpt_bcc_maps, 0, 0,
+ VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0,
+ VAR_MSG_REJECT_CHARS, DEF_MSG_REJECT_CHARS, &var_msg_reject_chars, 0, 0,
+ VAR_MSG_STRIP_CHARS, DEF_MSG_STRIP_CHARS, &var_msg_strip_chars, 0, 0,
+ VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0,
+ VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0,
+ VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0,
+ VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0,
+ VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0,
+ VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0,
+ VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0,
+ VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0,
+ VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0,
+ VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0,
+ VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0,
+ VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0,
+ VAR_CLEANUP_MILTERS, DEF_CLEANUP_MILTERS, &var_cleanup_milters, 0, 0,
+ VAR_MILT_HEAD_CHECKS, DEF_MILT_HEAD_CHECKS, &var_milt_head_checks, 0, 0,
+ VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0,
+ VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0,
+ 0,
+};
+
+ /*
+ * Mappings.
+ */
+MAPS *cleanup_comm_canon_maps;
+MAPS *cleanup_send_canon_maps;
+MAPS *cleanup_rcpt_canon_maps;
+int cleanup_comm_canon_flags;
+int cleanup_send_canon_flags;
+int cleanup_rcpt_canon_flags;
+MAPS *cleanup_header_checks;
+MAPS *cleanup_mimehdr_checks;
+MAPS *cleanup_nesthdr_checks;
+MAPS *cleanup_body_checks;
+MAPS *cleanup_virt_alias_maps;
+ARGV *cleanup_masq_domains;
+STRING_LIST *cleanup_masq_exceptions;
+int cleanup_masq_flags;
+MAPS *cleanup_send_bcc_maps;
+MAPS *cleanup_rcpt_bcc_maps;
+
+ /*
+ * Character filters.
+ */
+VSTRING *cleanup_reject_chars;
+VSTRING *cleanup_strip_chars;
+
+ /*
+ * Address extension propagation restrictions.
+ */
+int cleanup_ext_prop_mask;
+
+ /*
+ * Milter support.
+ */
+MILTERS *cleanup_milters;
+
+ /*
+ * From: header format.
+ */
+int hfrom_format_code;
+
+/* cleanup_all - callback for the runtime error handler */
+
+void cleanup_all(void)
+{
+ cleanup_sig(0);
+}
+
+/* cleanup_sig - callback for the SIGTERM handler */
+
+void cleanup_sig(int sig)
+{
+
+ /*
+ * msg_fatal() is safe against calling itself recursively, but signals
+ * need extra safety.
+ *
+ * XXX While running as a signal handler, can't ask the memory manager to
+ * release VSTRING storage.
+ */
+ if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+ if (cleanup_trace_path) {
+ (void) REMOVE(vstring_str(cleanup_trace_path));
+ cleanup_trace_path = 0;
+ }
+ if (cleanup_path) {
+ (void) REMOVE(cleanup_path);
+ cleanup_path = 0;
+ }
+ if (sig)
+ _exit(sig);
+ }
+}
+
+/* cleanup_pre_jail - initialize before entering the chroot jail */
+
+void cleanup_pre_jail(char *unused_name, char **unused_argv)
+{
+ static const NAME_MASK send_canon_class_table[] = {
+ CANON_CLASS_ENV_FROM, CLEANUP_CANON_FLAG_ENV_FROM,
+ CANON_CLASS_HDR_FROM, CLEANUP_CANON_FLAG_HDR_FROM,
+ 0,
+ };
+ static const NAME_MASK rcpt_canon_class_table[] = {
+ CANON_CLASS_ENV_RCPT, CLEANUP_CANON_FLAG_ENV_RCPT,
+ CANON_CLASS_HDR_RCPT, CLEANUP_CANON_FLAG_HDR_RCPT,
+ 0,
+ };
+ static const NAME_MASK canon_class_table[] = {
+ CANON_CLASS_ENV_FROM, CLEANUP_CANON_FLAG_ENV_FROM,
+ CANON_CLASS_ENV_RCPT, CLEANUP_CANON_FLAG_ENV_RCPT,
+ CANON_CLASS_HDR_FROM, CLEANUP_CANON_FLAG_HDR_FROM,
+ CANON_CLASS_HDR_RCPT, CLEANUP_CANON_FLAG_HDR_RCPT,
+ 0,
+ };
+ static const NAME_MASK masq_class_table[] = {
+ MASQ_CLASS_ENV_FROM, CLEANUP_MASQ_FLAG_ENV_FROM,
+ MASQ_CLASS_ENV_RCPT, CLEANUP_MASQ_FLAG_ENV_RCPT,
+ MASQ_CLASS_HDR_FROM, CLEANUP_MASQ_FLAG_HDR_FROM,
+ MASQ_CLASS_HDR_RCPT, CLEANUP_MASQ_FLAG_HDR_RCPT,
+ 0,
+ };
+
+ if (*var_canonical_maps)
+ cleanup_comm_canon_maps =
+ maps_create(VAR_CANONICAL_MAPS, var_canonical_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_send_canon_maps)
+ cleanup_send_canon_maps =
+ maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_rcpt_canon_maps)
+ cleanup_rcpt_canon_maps =
+ maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_virt_alias_maps)
+ cleanup_virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS,
+ var_virt_alias_maps,
+ DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_canon_classes)
+ cleanup_comm_canon_flags =
+ name_mask(VAR_CANON_CLASSES, canon_class_table,
+ var_canon_classes);
+ if (*var_send_canon_classes)
+ cleanup_send_canon_flags =
+ name_mask(VAR_CANON_CLASSES, send_canon_class_table,
+ var_send_canon_classes);
+ if (*var_rcpt_canon_classes)
+ cleanup_rcpt_canon_flags =
+ name_mask(VAR_CANON_CLASSES, rcpt_canon_class_table,
+ var_rcpt_canon_classes);
+ if (*var_masq_domains)
+ cleanup_masq_domains = argv_split(var_masq_domains, CHARS_COMMA_SP);
+ if (*var_header_checks)
+ cleanup_header_checks =
+ maps_create(VAR_HEADER_CHECKS, var_header_checks, DICT_FLAG_LOCK);
+ if (*var_mimehdr_checks)
+ cleanup_mimehdr_checks =
+ maps_create(VAR_MIMEHDR_CHECKS, var_mimehdr_checks, DICT_FLAG_LOCK);
+ if (*var_nesthdr_checks)
+ cleanup_nesthdr_checks =
+ maps_create(VAR_NESTHDR_CHECKS, var_nesthdr_checks, DICT_FLAG_LOCK);
+ if (*var_body_checks)
+ cleanup_body_checks =
+ maps_create(VAR_BODY_CHECKS, var_body_checks, DICT_FLAG_LOCK);
+ if (*var_masq_exceptions)
+ cleanup_masq_exceptions =
+ string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN,
+ var_masq_exceptions);
+ if (*var_masq_classes)
+ cleanup_masq_flags = name_mask(VAR_MASQ_CLASSES, masq_class_table,
+ var_masq_classes);
+ if (*var_send_bcc_maps)
+ cleanup_send_bcc_maps =
+ maps_create(VAR_SEND_BCC_MAPS, var_send_bcc_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_rcpt_bcc_maps)
+ cleanup_rcpt_bcc_maps =
+ maps_create(VAR_RCPT_BCC_MAPS, var_rcpt_bcc_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if (*var_cleanup_milters)
+ cleanup_milters = milter_create(var_cleanup_milters,
+ var_milt_conn_time,
+ var_milt_cmd_time,
+ var_milt_msg_time,
+ var_milt_protocol,
+ var_milt_def_action,
+ var_milt_conn_macros,
+ var_milt_helo_macros,
+ var_milt_mail_macros,
+ var_milt_rcpt_macros,
+ var_milt_data_macros,
+ var_milt_eoh_macros,
+ var_milt_eod_macros,
+ var_milt_unk_macros,
+ var_milt_macro_deflts);
+ if (*var_milt_head_checks)
+ cleanup_milter_header_checks_init();
+
+ flush_init();
+}
+
+/* cleanup_post_jail - initialize after entering the chroot jail */
+
+void cleanup_post_jail(char *unused_name, char **unused_argv)
+{
+ static const NAME_CODE hfrom_format_table[] = {
+ HFROM_FORMAT_NAME_STD, HFROM_FORMAT_CODE_STD,
+ HFROM_FORMAT_NAME_OBS, HFROM_FORMAT_CODE_OBS,
+ 0, -1,
+ };
+
+ /*
+ * Optionally set the file size resource limit. XXX This limits the
+ * message content to somewhat less than requested, because the total
+ * queue file size also includes envelope information. Unless people set
+ * really low limit, the difference is going to matter only when a queue
+ * file has lots of recipients.
+ */
+ if (ENFORCING_SIZE_LIMIT(var_message_limit))
+ set_file_limit((off_t) var_message_limit);
+
+ /*
+ * Control how unmatched extensions are propagated.
+ */
+ cleanup_ext_prop_mask =
+ ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension);
+
+ /*
+ * Setup the filters for characters that should be rejected, and for
+ * characters that should be removed.
+ */
+ if (*var_msg_reject_chars) {
+ cleanup_reject_chars = vstring_alloc(strlen(var_msg_reject_chars));
+ unescape(cleanup_reject_chars, var_msg_reject_chars);
+ }
+ if (*var_msg_strip_chars) {
+ cleanup_strip_chars = vstring_alloc(strlen(var_msg_strip_chars));
+ unescape(cleanup_strip_chars, var_msg_strip_chars);
+ }
+
+ /*
+ * From: header formatting.
+ */
+ if ((hfrom_format_code = name_code(hfrom_format_table,
+ NAME_CODE_FLAG_NONE, var_hfrom_format)) < 0)
+ msg_fatal("invalid setting: %s = %s",
+ VAR_HFROM_FORMAT, var_hfrom_format);
+}
diff --git a/src/cleanup/cleanup_map11.c b/src/cleanup/cleanup_map11.c
new file mode 100644
index 0000000..4934aac
--- /dev/null
+++ b/src/cleanup/cleanup_map11.c
@@ -0,0 +1,183 @@
+/*++
+/* NAME
+/* cleanup_map11 3
+/* SUMMARY
+/* one-to-one mapping
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* int cleanup_map11_external(state, addr, maps, propagate)
+/* CLEANUP_STATE *state;
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int cleanup_map11_internal(state, addr, maps, propagate)
+/* CLEANUP_STATE *state;
+/* VSTRING *addr;
+/* MAPS *maps;
+/* int propagate;
+/*
+/* int cleanup_map11_tree(state, tree, maps, propagate)
+/* CLEANUP_STATE *state;
+/* TOK822 *tree;
+/* MAPS *maps;
+/* int propagate;
+/* DESCRIPTION
+/* This module performs one-to-one map lookups.
+/*
+/* If an address has a mapping, the lookup result is
+/* subjected to another iteration of rewriting and mapping.
+/* Recursion continues until an address maps onto itself,
+/* or until an unreasonable recursion level is reached.
+/* An unmatched address extension is propagated when
+/* \fIpropagate\fR is non-zero.
+/* These functions return non-zero when the address was changed.
+/*
+/* cleanup_map11_external() looks up the external (quoted) string
+/* form of an address in the maps specified via the \fImaps\fR argument.
+/*
+/* cleanup_map11_internal() is a wrapper around the
+/* cleanup_map11_external() routine that transforms from
+/* internal (quoted) string form to external form and back.
+/*
+/* cleanup_map11_tree() is a wrapper around the
+/* cleanup_map11_external() routine that transforms from
+/* internal parse tree form to external form and back.
+/* DIAGNOSTICS
+/* Recoverable errors: the global \fIcleanup_errs\fR flag is updated.
+/* SEE ALSO
+/* mail_addr_find(3) address lookups
+/* mail_addr_map(3) address mappings
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <cleanup_user.h>
+#include <mail_addr_map.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+#define MAX_RECURSION 10
+
+/* cleanup_map11_external - one-to-one table lookups */
+
+int cleanup_map11_external(CLEANUP_STATE *state, VSTRING *addr,
+ MAPS *maps, int propagate)
+{
+ int count;
+ int expand_to_self;
+ ARGV *new_addr;
+ char *saved_addr;
+ int did_rewrite = 0;
+
+ /*
+ * Produce sensible output even in the face of a recoverable error. This
+ * simplifies error recovery considerably because we can do delayed error
+ * checking in one place, instead of having error handling code all over
+ * the place.
+ */
+ for (count = 0; count < MAX_RECURSION; count++) {
+ if ((new_addr = mail_addr_map_opt(maps, STR(addr), propagate,
+ MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
+ MA_FORM_EXTERNAL)) != 0) {
+ if (new_addr->argc > 1)
+ msg_warn("%s: multi-valued %s entry for %s",
+ state->queue_id, maps->title, STR(addr));
+ saved_addr = mystrdup(STR(addr));
+ did_rewrite |= strcmp(new_addr->argv[0], STR(addr));
+ vstring_strcpy(addr, new_addr->argv[0]);
+ expand_to_self = !strcasecmp_utf8(saved_addr, STR(addr));
+ myfree(saved_addr);
+ argv_free(new_addr);
+ if (expand_to_self)
+ return (did_rewrite);
+ } else if (maps->error != 0) {
+ msg_warn("%s: %s map lookup problem for %s -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, STR(addr));
+ state->errs |= CLEANUP_STAT_WRITE;
+ return (did_rewrite);
+ } else {
+ return (did_rewrite);
+ }
+ }
+ msg_warn("%s: unreasonable %s map nesting for %s -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, STR(addr));
+ return (did_rewrite);
+}
+
+/* cleanup_map11_tree - rewrite address node */
+
+int cleanup_map11_tree(CLEANUP_STATE *state, TOK822 *tree,
+ MAPS *maps, int propagate)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int did_rewrite;
+
+ /*
+ * Produce sensible output even in the face of a recoverable error. This
+ * simplifies error recovery considerably because we can do delayed error
+ * checking in one place, instead of having error handling code all over
+ * the place.
+ */
+ tok822_externalize(temp, tree->head, TOK822_STR_DEFL);
+ did_rewrite = cleanup_map11_external(state, temp, maps, propagate);
+ tok822_free_tree(tree->head);
+ tree->head = tok822_scan(STR(temp), &tree->tail);
+ vstring_free(temp);
+ return (did_rewrite);
+}
+
+/* cleanup_map11_internal - rewrite address internal form */
+
+int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
+ MAPS *maps, int propagate)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int did_rewrite;
+
+ /*
+ * Produce sensible output even in the face of a recoverable error. This
+ * simplifies error recovery considerably because we can do delayed error
+ * checking in one place, instead of having error handling code all over
+ * the place.
+ */
+ quote_822_local(temp, STR(addr));
+ did_rewrite = cleanup_map11_external(state, temp, maps, propagate);
+ unquote_822_local(addr, STR(temp));
+ vstring_free(temp);
+ return (did_rewrite);
+}
diff --git a/src/cleanup/cleanup_map1n.c b/src/cleanup/cleanup_map1n.c
new file mode 100644
index 0000000..9cecfcb
--- /dev/null
+++ b/src/cleanup/cleanup_map1n.c
@@ -0,0 +1,182 @@
+/*++
+/* NAME
+/* cleanup_map1n 3
+/* SUMMARY
+/* one-to-many address mapping
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* ARGV *cleanup_map1n_internal(state, addr, maps, propagate)
+/* CLEANUP_STATE *state;
+/* const char *addr;
+/* MAPS *maps;
+/* int propagate;
+/* DESCRIPTION
+/* This module implements one-to-many table mapping via table lookup.
+/* Table lookups are done with quoted (externalized) address forms.
+/* The process is recursive. The recursion terminates when the
+/* left-hand side appears in its own expansion.
+/*
+/* cleanup_map1n_internal() is the interface for addresses in
+/* internal (unquoted) form.
+/* DIAGNOSTICS
+/* When the maximal expansion or recursion limit is reached,
+/* the alias is not expanded and the CLEANUP_STAT_DEFER error
+/* is raised with reason "4.6.0 Alias expansion error".
+/*
+/* When table lookup fails, the alias is not expanded and the
+/* CLEANUP_STAT_WRITE error is raised with reason "4.6.0 Alias
+/* expansion error".
+/* SEE ALSO
+/* mail_addr_map(3) address mappings
+/* mail_addr_find(3) address lookups
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <argv.h>
+#include <vstring.h>
+#include <dict.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_addr_map.h>
+#include <cleanup_user.h>
+#include <quote_822_local.h>
+#include <been_here.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_map1n_internal - one-to-many table lookups */
+
+ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr,
+ MAPS *maps, int propagate)
+{
+ ARGV *argv;
+ ARGV *lookup;
+ int count;
+ int i;
+ int arg;
+ BH_TABLE *been_here;
+ char *saved_lhs;
+
+ /*
+ * Initialize.
+ */
+ argv = argv_alloc(1);
+ argv_add(argv, addr, ARGV_END);
+ argv_terminate(argv);
+ been_here = been_here_init(0, BH_FLAG_FOLD);
+
+ /*
+ * Rewrite the address vector in place. With each map lookup result,
+ * split it into separate addresses, then rewrite and flatten each
+ * address, and repeat the process. Beware: argv is being changed, so we
+ * must index the array explicitly, instead of running along it with a
+ * pointer.
+ */
+#define UPDATE(ptr,new) do { \
+ if (ptr) myfree(ptr); ptr = mystrdup(new); \
+ } while (0)
+#define STR vstring_str
+#define RETURN(x) do { \
+ been_here_free(been_here); return (x); \
+ } while (0)
+#define UNEXPAND(argv, addr) do { \
+ argv_truncate((argv), 0); argv_add((argv), (addr), (char *) 0); \
+ } while (0)
+
+ for (arg = 0; arg < argv->argc; arg++) {
+ if (argv->argc > var_virt_expan_limit) {
+ msg_warn("%s: unreasonable %s map expansion size for %s -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, addr);
+ state->errs |= CLEANUP_STAT_DEFER;
+ UPDATE(state->reason, "4.6.0 Alias expansion error");
+ UNEXPAND(argv, addr);
+ RETURN(argv);
+ }
+ for (count = 0; /* void */ ; count++) {
+
+ /*
+ * Don't expand an address that already expanded into itself.
+ */
+ if (been_here_check_fixed(been_here, argv->argv[arg]) != 0)
+ break;
+ if (count >= var_virt_recur_limit) {
+ msg_warn("%s: unreasonable %s map nesting for %s -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, addr);
+ state->errs |= CLEANUP_STAT_DEFER;
+ UPDATE(state->reason, "4.6.0 Alias expansion error");
+ UNEXPAND(argv, addr);
+ RETURN(argv);
+ }
+ if ((lookup = mail_addr_map_internal(maps, argv->argv[arg],
+ propagate)) != 0) {
+ saved_lhs = mystrdup(argv->argv[arg]);
+ for (i = 0; i < lookup->argc; i++) {
+ if (strlen(lookup->argv[i]) > var_virt_addrlen_limit) {
+ msg_warn("%s: unreasonable %s result %.300s... -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, lookup->argv[i]);
+ state->errs |= CLEANUP_STAT_DEFER;
+ UPDATE(state->reason, "4.6.0 Alias expansion error");
+ UNEXPAND(argv, addr);
+ RETURN(argv);
+ }
+ if (i == 0) {
+ UPDATE(argv->argv[arg], lookup->argv[i]);
+ } else {
+ argv_add(argv, lookup->argv[i], ARGV_END);
+ argv_terminate(argv);
+ }
+
+ /*
+ * Allow an address to expand into itself once.
+ */
+ if (strcasecmp_utf8(saved_lhs, lookup->argv[i]) == 0)
+ been_here_fixed(been_here, saved_lhs);
+ }
+ myfree(saved_lhs);
+ argv_free(lookup);
+ } else if (maps->error != 0) {
+ msg_warn("%s: %s map lookup problem for %s -- "
+ "message not accepted, try again later",
+ state->queue_id, maps->title, addr);
+ state->errs |= CLEANUP_STAT_WRITE;
+ UPDATE(state->reason, "4.6.0 Alias expansion error");
+ UNEXPAND(argv, addr);
+ RETURN(argv);
+ } else {
+ break;
+ }
+ }
+ }
+ RETURN(argv);
+}
diff --git a/src/cleanup/cleanup_masq.ref b/src/cleanup/cleanup_masq.ref
new file mode 100644
index 0000000..6838894
--- /dev/null
+++ b/src/cleanup/cleanup_masq.ref
@@ -0,0 +1,66 @@
+----------
+exceptions:
+masq_list: a.b.c,b.c
+address: xxx@aa.a.b.c
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: A.B.C,B.C
+address: xxx@aa.a.b.c
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: a.b.c,b.c
+address: xxx@AA.A.B.C
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions: xxx
+masq_list: a.b.c,b.c
+address: xxx@aa.a.b.c
+result: xxx@aa.a.b.c
+errs: 0
+----------
+exceptions: yyy
+masq_list: a.b.c,b.c
+address: xxx@aa.a.b.c
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: !a.b.c,b.c
+address: xxx@aa.a.b.c
+result: xxx@aa.a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: a.b.c,b.c
+address: xxx@a.b.c
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: !a.b.c,b.c
+address: xxx@a.b.c
+result: xxx@a.b.c
+errs: 0
+----------
+exceptions:
+masq_list: a.b.c,b.c
+address: xxx@aaa.b.c
+result: xxx@b.c
+errs: 0
+----------
+exceptions:
+masq_list: a.b.c,b.c
+address: xxx@b.c
+result: xxx@b.c
+errs: 0
+----------
+exceptions: fail:whatever
+masq_list: xy
+address: xxx@b.c
+result: xxx@b.c
+errs: 2
diff --git a/src/cleanup/cleanup_masquerade.c b/src/cleanup/cleanup_masquerade.c
new file mode 100644
index 0000000..4fc3a13
--- /dev/null
+++ b/src/cleanup/cleanup_masquerade.c
@@ -0,0 +1,239 @@
+/*++
+/* NAME
+/* cleanup_masquerade 3
+/* SUMMARY
+/* address masquerading
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* int cleanup_masquerade_external(addr, masq_domains)
+/* VSTRING *addr;
+/* ARGV *masq_domains;
+/*
+/* int cleanup_masquerade_internal(addr, masq_domains)
+/* VSTRING *addr;
+/* ARGV *masq_domains;
+/*
+/* int cleanup_masquerade_tree(tree, masq_domains)
+/* TOK822 *tree;
+/* ARGV *masq_domains;
+/* DESCRIPTION
+/* This module masquerades addresses, that is, it strips subdomains
+/* below domain names that are listed in the masquerade_domains
+/* configuration parameter, except for user names listed in the
+/* masquerade_exceptions configuration parameter.
+/* These functions return non-zero when the address was changed.
+/*
+/* cleanup_masquerade_external() rewrites the external (quoted) string
+/* form of an address.
+/*
+/* cleanup_masquerade_internal() is a wrapper around the
+/* cleanup_masquerade_external() routine that transforms from
+/* internal (quoted) string form to external form and back.
+/*
+/* cleanup_masquerade_tree() is a wrapper around the
+/* cleanup_masquerade_external() routine that transforms from
+/* internal parse tree form to external form and back.
+/* DIAGNOSTICS
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <argv.h>
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <tok822.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+
+/* cleanup_masquerade_external - masquerade address external form */
+
+int cleanup_masquerade_external(CLEANUP_STATE *state, VSTRING *addr,
+ ARGV *masq_domains)
+{
+ char *domain;
+ ssize_t domain_len;
+ char **masqp;
+ char *masq;
+ ssize_t masq_len;
+ char *parent;
+ int truncate;
+ int did_rewrite = 0;
+
+ /* Stuff for excluded names. */
+ char *name;
+ ssize_t name_len;
+ int excluded;
+
+ /*
+ * Find the domain part.
+ */
+ if ((domain = strrchr(STR(addr), '@')) == 0)
+ return (0);
+ name_len = domain - STR(addr);
+ domain = casefold(state->temp2, domain + 1);
+ domain_len = strlen(domain);
+
+ /*
+ * Don't masquerade excluded names (regardless of domain).
+ */
+ if (*var_masq_exceptions) {
+ name = mystrndup(STR(addr), name_len);
+ excluded = (string_list_match(cleanup_masq_exceptions, name) != 0);
+ myfree(name);
+ if (cleanup_masq_exceptions->error) {
+ msg_info("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, VAR_MASQ_EXCEPTIONS);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ if (excluded)
+ return (0);
+ }
+
+ /*
+ * If any parent domain matches the list of masquerade domains, replace
+ * the domain in the address and terminate. If the domain matches a
+ * masquerade domain, leave it alone. Order of specification matters.
+ */
+ for (masqp = masq_domains->argv; (masq = *masqp) != 0; masqp++) {
+ for (truncate = 1; *masq == '!'; masq++)
+ truncate = !truncate;
+ masq = casefold(state->temp1, masq);
+ masq_len = strlen(masq);
+ if (masq_len == 0)
+ continue;
+ if (masq_len == domain_len) {
+ if (strcmp(masq, domain) == 0)
+ break;
+ } else if (masq_len < domain_len) {
+ parent = domain + domain_len - masq_len;
+ if (parent[-1] == '.' && strcmp(masq, parent) == 0) {
+ if (truncate) {
+ if (msg_verbose)
+ msg_info("masquerade: %s -> %s", domain, masq);
+ vstring_truncate(addr, name_len + 1);
+ vstring_strcat(addr, masq);
+ did_rewrite = 1;
+ }
+ break;
+ }
+ }
+ }
+ return (did_rewrite);
+}
+
+/* cleanup_masquerade_tree - masquerade address node */
+
+int cleanup_masquerade_tree(CLEANUP_STATE *state, TOK822 *tree,
+ ARGV *masq_domains)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int did_rewrite;
+
+ tok822_externalize(temp, tree->head, TOK822_STR_DEFL);
+ did_rewrite = cleanup_masquerade_external(state, temp, masq_domains);
+ tok822_free_tree(tree->head);
+ tree->head = tok822_scan(STR(temp), &tree->tail);
+
+ vstring_free(temp);
+ return (did_rewrite);
+}
+
+/* cleanup_masquerade_internal - masquerade address internal form */
+
+int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr,
+ ARGV *masq_domains)
+{
+ VSTRING *temp = vstring_alloc(100);
+ int did_rewrite;
+
+ quote_822_local(temp, STR(addr));
+ did_rewrite = cleanup_masquerade_external(state, temp, masq_domains);
+ unquote_822_local(addr, STR(temp));
+
+ vstring_free(temp);
+ return (did_rewrite);
+}
+
+ /*
+ * Code for stand-alone testing. Instead of using main.cf, specify the strip
+ * list and the candidate domain on the command line. Specify null arguments
+ * for data that should be empty.
+ */
+#ifdef TEST
+
+#include <vstream.h>
+
+char *var_masq_exceptions;
+STRING_LIST *cleanup_masq_exceptions;
+
+int main(int argc, char **argv)
+{
+ VSTRING *addr;
+ ARGV *masq_domains;
+ CLEANUP_STATE state;
+
+ if (argc != 4)
+ msg_fatal("usage: %s exceptions masquerade_list address", argv[0]);
+
+ var_masq_exceptions = argv[1];
+ cleanup_masq_exceptions =
+ string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN,
+ var_masq_exceptions);
+ masq_domains = argv_split(argv[2], CHARS_COMMA_SP);
+ addr = vstring_alloc(1);
+ if (strchr(argv[3], '@') == 0)
+ msg_fatal("address must be in user@domain form");
+ vstring_strcpy(addr, argv[3]);
+
+ vstream_printf("----------\n");
+ vstream_printf("exceptions: %s\n", argv[1]);
+ vstream_printf("masq_list: %s\n", argv[2]);
+ vstream_printf("address: %s\n", argv[3]);
+
+ state.errs = 0;
+ state.queue_id = "NOQUEUE";
+ state.temp1 = vstring_alloc(100);
+ state.temp2 = vstring_alloc(100);
+ cleanup_masquerade_external(&state, addr, masq_domains);
+
+ vstream_printf("result: %s\n", STR(addr));
+ vstream_printf("errs: %d\n", state.errs);
+ vstream_fflush(VSTREAM_OUT);
+
+ vstring_free(state.temp1);
+ vstring_free(state.temp2);
+ vstring_free(addr);
+ argv_free(masq_domains);
+
+ return (0);
+}
+
+#endif
diff --git a/src/cleanup/cleanup_message.c b/src/cleanup/cleanup_message.c
new file mode 100644
index 0000000..4284c46
--- /dev/null
+++ b/src/cleanup/cleanup_message.c
@@ -0,0 +1,1116 @@
+/*++
+/* NAME
+/* cleanup_message 3
+/* SUMMARY
+/* process message segment
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_message(state, type, buf, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *buf;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module processes message content records and copies the
+/* result to the queue file. It validates the input, rewrites
+/* sender/recipient addresses to canonical form, inserts missing
+/* message headers, and extracts information from message headers
+/* to be used later when generating the extracted output segment.
+/* This routine absorbs but does not emit the content to extracted
+/* boundary record.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is updated
+/* as records are processed and as errors happen.
+/* .IP type
+/* Record type.
+/* .IP buf
+/* Record content.
+/* .IP len
+/* Record content length.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <argv.h>
+#include <split_at.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <nvtable.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <cleanup_user.h>
+#include <tok822.h>
+#include <lex_822.h>
+#include <header_opts.h>
+#include <quote_822_local.h>
+#include <mail_params.h>
+#include <mail_date.h>
+#include <mail_addr.h>
+#include <is_header.h>
+#include <ext_prop.h>
+#include <mail_proto.h>
+#include <mime_state.h>
+#include <lex_822.h>
+#include <dsn_util.h>
+#include <conv_time.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_fold_header - wrap address list header */
+
+static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf)
+{
+ char *start_line = vstring_str(header_buf);
+ char *end_line;
+ char *next_line;
+ char *line;
+
+ /*
+ * A rewritten address list contains one address per line. The code below
+ * replaces newlines by spaces, to fit as many addresses on a line as
+ * possible (without rearranging the order of addresses). Prepending
+ * white space to the beginning of lines is delegated to the output
+ * routine.
+ */
+ for (line = start_line; line != 0; line = next_line) {
+ end_line = line + strcspn(line, "\n");
+ if (line > start_line) {
+ if (end_line - start_line < 70) { /* TAB counts as one */
+ line[-1] = ' ';
+ } else {
+ start_line = line;
+ }
+ }
+ next_line = *end_line ? end_line + 1 : 0;
+ }
+ cleanup_out_header(state, header_buf);
+}
+
+/* cleanup_extract_internal - save unquoted copy of extracted address */
+
+static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr)
+{
+
+ /*
+ * A little routine to stash away a copy of an address that we extracted
+ * from a message header line.
+ */
+ tok822_internalize(buffer, addr->head, TOK822_STR_DEFL);
+ return (mystrdup(vstring_str(buffer)));
+}
+
+/* cleanup_rewrite_sender - sender address rewriting */
+
+static void cleanup_rewrite_sender(CLEANUP_STATE *state,
+ const HEADER_OPTS *hdr_opts,
+ VSTRING *header_buf)
+{
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+ int did_rewrite = 0;
+
+ if (msg_verbose)
+ msg_info("rewrite_sender: %s", hdr_opts->name);
+
+ /*
+ * Parse the header line, rewrite each address found, and regenerate the
+ * header line. Finally, pipe the result through the header line folding
+ * routine.
+ */
+ tree = tok822_parse_limit(vstring_str(header_buf)
+ + strlen(hdr_opts->name) + 1,
+ var_token_limit);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++) {
+ did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
+ if (state->flags & CLEANUP_FLAG_MAP_OK) {
+ if (cleanup_send_canon_maps
+ && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
+ did_rewrite |=
+ cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps
+ && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM))
+ did_rewrite |=
+ cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_masq_domains
+ && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM))
+ did_rewrite |=
+ cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
+ }
+ }
+ if (did_rewrite) {
+ vstring_truncate(header_buf, strlen(hdr_opts->name));
+ vstring_strcat(header_buf, ": ");
+ tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
+ }
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
+ if (did_rewrite)
+ cleanup_fold_header(state, header_buf);
+ else
+ cleanup_out_header(state, header_buf);
+ }
+}
+
+/* cleanup_rewrite_recip - recipient address rewriting */
+
+static void cleanup_rewrite_recip(CLEANUP_STATE *state,
+ const HEADER_OPTS *hdr_opts,
+ VSTRING *header_buf)
+{
+ TOK822 *tree;
+ TOK822 **addr_list;
+ TOK822 **tpp;
+ int did_rewrite = 0;
+
+ if (msg_verbose)
+ msg_info("rewrite_recip: %s", hdr_opts->name);
+
+ /*
+ * Parse the header line, rewrite each address found, and regenerate the
+ * header line. Finally, pipe the result through the header line folding
+ * routine.
+ */
+ tree = tok822_parse_limit(vstring_str(header_buf)
+ + strlen(hdr_opts->name) + 1,
+ var_token_limit);
+ addr_list = tok822_grep(tree, TOK822_ADDR);
+ for (tpp = addr_list; *tpp; tpp++) {
+ did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp);
+ if (state->flags & CLEANUP_FLAG_MAP_OK) {
+ if (cleanup_rcpt_canon_maps
+ && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
+ did_rewrite |=
+ cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_comm_canon_maps
+ && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT))
+ did_rewrite |=
+ cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps,
+ cleanup_ext_prop_mask & EXT_PROP_CANONICAL);
+ if (cleanup_masq_domains
+ && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT))
+ did_rewrite |=
+ cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains);
+ }
+ }
+ if (did_rewrite) {
+ vstring_truncate(header_buf, strlen(hdr_opts->name));
+ vstring_strcat(header_buf, ": ");
+ tok822_externalize(header_buf, tree, TOK822_STR_HEAD);
+ }
+ myfree((void *) addr_list);
+ tok822_free_tree(tree);
+ if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
+ if (did_rewrite)
+ cleanup_fold_header(state, header_buf);
+ else
+ cleanup_out_header(state, header_buf);
+ }
+}
+
+/* cleanup_act_log - log action with context */
+
+static void cleanup_act_log(CLEANUP_STATE *state,
+ const char *action, const char *class,
+ const char *content, const char *text)
+{
+ const char *attr;
+
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
+ attr = "unknown";
+ vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;",
+ state->queue_id, action, class, content, attr);
+ if (state->sender)
+ vstring_sprintf_append(state->temp1, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recip)
+ vstring_sprintf_append(state->temp1, " to=<%s>",
+ info_log_addr_form_recipient(state->recip));
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " proto=%s", attr);
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
+ if (text && *text)
+ vstring_sprintf_append(state->temp1, ": %s", text);
+ msg_info("%s", vstring_str(state->temp1));
+}
+
+#define CLEANUP_ACT_CTXT_HEADER "header"
+#define CLEANUP_ACT_CTXT_BODY "body"
+#define CLEANUP_ACT_CTXT_ANY "content"
+
+/* cleanup_act - act upon a header/body match */
+
+static const char *cleanup_act(CLEANUP_STATE *state, char *context,
+ const char *buf, const char *value,
+ const char *map_class)
+{
+ const char *optional_text = value + strcspn(value, " \t");
+ int command_len = optional_text - value;
+
+#ifdef DELAY_ACTION
+ int defer_delay;
+
+#endif
+
+ while (*optional_text && ISSPACE(*optional_text))
+ optional_text++;
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+#define CLEANUP_ACT_DROP 0
+
+ /*
+ * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
+ * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
+ * queue record processing, and prevents bounces from being sent.
+ */
+ if (STREQUAL(value, "REJECT", command_len)) {
+ const CLEANUP_STAT_DETAIL *detail;
+
+ if (state->reason)
+ myfree(state->reason);
+ if (*optional_text) {
+ state->reason = dsn_prepend("5.7.1", optional_text);
+ if (*state->reason != '4' && *state->reason != '5') {
+ msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
+ optional_text);
+ *state->reason = '4';
+ }
+ } else {
+ detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
+ state->reason = dsn_prepend(detail->dsn, detail->text);
+ }
+ if (*state->reason == '4')
+ state->errs |= CLEANUP_STAT_DEFER;
+ else
+ state->errs |= CLEANUP_STAT_CONT;
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ cleanup_act_log(state, "reject", context, buf, state->reason);
+ return (buf);
+ }
+ if (STREQUAL(value, "WARN", command_len)) {
+ cleanup_act_log(state, "warning", context, buf, optional_text);
+ return (buf);
+ }
+ if (STREQUAL(value, "INFO", command_len)) {
+ cleanup_act_log(state, "info", context, buf, optional_text);
+ return (buf);
+ }
+ if (STREQUAL(value, "FILTER", command_len)) {
+ if (*optional_text == 0) {
+ msg_warn("missing FILTER command argument in %s map", map_class);
+ } else if (strchr(optional_text, ':') == 0) {
+ msg_warn("bad FILTER command %s in %s -- "
+ "need transport:destination",
+ optional_text, map_class);
+ } else {
+ if (state->filter)
+ myfree(state->filter);
+ state->filter = mystrdup(optional_text);
+ cleanup_act_log(state, "filter", context, buf, optional_text);
+ }
+ return (buf);
+ }
+ if (STREQUAL(value, "PASS", command_len)) {
+ cleanup_act_log(state, "pass", context, buf, optional_text);
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ return (buf);
+ }
+ if (STREQUAL(value, "DISCARD", command_len)) {
+ cleanup_act_log(state, "discard", context, buf, optional_text);
+ state->flags |= CLEANUP_FLAG_DISCARD;
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ return (buf);
+ }
+ if (STREQUAL(value, "HOLD", command_len)) {
+ if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
+ cleanup_act_log(state, "hold", context, buf, optional_text);
+ state->flags |= CLEANUP_FLAG_HOLD;
+ }
+ return (buf);
+ }
+
+ /*
+ * The DELAY feature is disabled because it has too many problems. 1) It
+ * does not work on some remote file systems; 2) mail will be delivered
+ * anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs
+ * down the deferred queue scan with huge amounts of useless disk I/O
+ * operations.
+ */
+#ifdef DELAY_ACTION
+ if (STREQUAL(value, "DELAY", command_len)) {
+ if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
+ if (*optional_text == 0) {
+ msg_warn("missing DELAY argument in %s map", map_class);
+ } else if (conv_time(optional_text, &defer_delay, 's') == 0) {
+ msg_warn("ignoring bad DELAY argument %s in %s map",
+ optional_text, map_class);
+ } else {
+ cleanup_act_log(state, "delay", context, buf, optional_text);
+ state->defer_delay = defer_delay;
+ }
+ }
+ return (buf);
+ }
+#endif
+ if (STREQUAL(value, "PREPEND", command_len)) {
+ if (*optional_text == 0) {
+ msg_warn("PREPEND action without text in %s map", map_class);
+ } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) {
+ if (!is_header(optional_text)) {
+ msg_warn("bad PREPEND header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"",
+ optional_text, map_class);
+ }
+
+ /*
+ * By design, cleanup_out_header() may modify content. Play safe
+ * and prepare for future developments.
+ */
+ else {
+ VSTRING *temp;
+
+ cleanup_act_log(state, "prepend", context, buf, optional_text);
+ temp = vstring_strcpy(vstring_alloc(strlen(optional_text)),
+ optional_text);
+ cleanup_out_header(state, temp);
+ vstring_free(temp);
+ }
+ } else {
+ cleanup_act_log(state, "prepend", context, buf, optional_text);
+ cleanup_out_string(state, REC_TYPE_NORM, optional_text);
+ }
+ return (buf);
+ }
+ if (STREQUAL(value, "REPLACE", command_len)) {
+ if (*optional_text == 0) {
+ msg_warn("REPLACE action without text in %s map", map_class);
+ return (buf);
+ } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0
+ && !is_header(optional_text)) {
+ msg_warn("bad REPLACE header text \"%s\" in %s map -- "
+ "need \"headername: headervalue\"",
+ optional_text, map_class);
+ return (buf);
+ } else {
+ cleanup_act_log(state, "replace", context, buf, optional_text);
+ return (mystrdup(optional_text));
+ }
+ }
+ if (STREQUAL(value, "REDIRECT", command_len)) {
+ if (strchr(optional_text, '@') == 0) {
+ msg_warn("bad REDIRECT target \"%s\" in %s map -- "
+ "need user@domain",
+ optional_text, map_class);
+ } else {
+ if (state->redirect)
+ myfree(state->redirect);
+ state->redirect = mystrdup(optional_text);
+ cleanup_act_log(state, "redirect", context, buf, optional_text);
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ }
+ return (buf);
+ }
+ if (STREQUAL(value, "BCC", command_len)) {
+ if (strchr(optional_text, '@') == 0) {
+ msg_warn("bad BCC address \"%s\" in %s map -- "
+ "need user@domain",
+ optional_text, map_class);
+ } else {
+ if (state->hbc_rcpt == 0)
+ state->hbc_rcpt = argv_alloc(1);
+ argv_add(state->hbc_rcpt, optional_text, (char *) 0);
+ cleanup_act_log(state, "bcc", context, buf, optional_text);
+ }
+ return (buf);
+ }
+ if (STREQUAL(value, "STRIP", command_len)) {
+ cleanup_act_log(state, "strip", context, buf, optional_text);
+ return (CLEANUP_ACT_DROP);
+ }
+ /* Allow and ignore optional text after the action. */
+
+ if (STREQUAL(value, "IGNORE", command_len))
+ return (CLEANUP_ACT_DROP);
+
+ if (STREQUAL(value, "DUNNO", command_len)) /* preferred */
+ return (buf);
+
+ if (STREQUAL(value, "OK", command_len)) /* compat */
+ return (buf);
+
+ msg_warn("unknown command in %s map: %s", map_class, value);
+ return (buf);
+}
+
+/* cleanup_header_callback - process one complete header line */
+
+static void cleanup_header_callback(void *context, int header_class,
+ const HEADER_OPTS *hdr_opts,
+ VSTRING *header_buf,
+ off_t unused_offset)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ const char *myname = "cleanup_header_callback";
+ char *hdrval;
+ struct code_map {
+ const char *name;
+ const char *encoding;
+ };
+ static struct code_map code_map[] = { /* RFC 2045 */
+ "7bit", MAIL_ATTR_ENC_7BIT,
+ "8bit", MAIL_ATTR_ENC_8BIT,
+ "binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */
+ "quoted-printable", MAIL_ATTR_ENC_7BIT,
+ "base64", MAIL_ATTR_ENC_7BIT,
+ 0,
+ };
+ struct code_map *cmp;
+ MAPS *checks;
+ const char *map_class;
+
+ if (msg_verbose)
+ msg_info("%s: '%.200s'", myname, vstring_str(header_buf));
+
+ /*
+ * Crude header filtering. This stops malware that isn't sophisticated
+ * enough to use fancy header encodings.
+ */
+#define CHECK(class, maps, var_name) \
+ (header_class == class && (map_class = var_name, checks = maps) != 0)
+
+ if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
+ header_class = MIME_HDR_MULTIPART;
+
+ /* Update the Received: header count before maybe dropping headers below. */
+ if (hdr_opts && hdr_opts->type == HDR_RECEIVED)
+ state->hop_count += 1;
+
+ if ((state->flags & CLEANUP_FLAG_FILTER)
+ && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS)
+ || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS)
+ || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) {
+ char *header = vstring_str(header_buf);
+ const char *value;
+
+ if ((value = maps_find(checks, header, 0)) != 0) {
+ const char *result;
+
+ if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER,
+ header, value, map_class))
+ == CLEANUP_ACT_DROP) {
+ return;
+ } else if (result != header) {
+ vstring_strcpy(header_buf, result);
+ hdr_opts = header_opts_find(result);
+ myfree((void *) result);
+ }
+ } else if (checks->error) {
+ msg_warn("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, checks->title);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+
+ /*
+ * If this is an "unknown" header, just copy it to the output without
+ * even bothering to fold long lines. cleanup_out() will split long
+ * headers that do not fit a REC_TYPE_NORM record.
+ */
+ if (hdr_opts == 0) {
+ cleanup_out_header(state, header_buf);
+ return;
+ }
+
+ /*
+ * Allow 8-bit type info to override 7-bit type info. XXX Should reuse
+ * the effort that went into MIME header parsing.
+ */
+ hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1;
+ while (ISSPACE(*hdrval))
+ hdrval++;
+ /* trimblanks(hdrval, 0)[0] = 0; */
+ if (var_auto_8bit_enc_hdr
+ && hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) {
+ for (cmp = code_map; cmp->name != 0; cmp++) {
+ if (strcasecmp(hdrval, cmp->name) == 0) {
+ if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0)
+ nvtable_update(state->attr, MAIL_ATTR_ENCODING,
+ cmp->encoding);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Copy attachment etc. header blocks without further inspection.
+ */
+ if (header_class != MIME_HDR_PRIMARY) {
+ cleanup_out_header(state, header_buf);
+ return;
+ }
+
+ /*
+ * Known header. Remember that we have seen at least one. Find out what
+ * we should do with this header: delete, count, rewrite. Note that we
+ * should examine headers even when they will be deleted from the output,
+ * because the addresses in those headers might be needed elsewhere.
+ *
+ * XXX 2821: Return-path breakage.
+ *
+ * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a
+ * message it inserts at the beginning of the mail data a return path
+ * line. The return path line preserves the information in the
+ * <reverse-path> from the MAIL command. Here, final delivery means the
+ * message leaves the SMTP world. Normally, this would mean it has been
+ * delivered to the destination user, but in some cases it may be further
+ * processed and transmitted by another mail system.
+ *
+ * And that is what Postfix implements. Delivery agents prepend
+ * Return-Path:. In order to avoid cluttering up the message with
+ * possibly inconsistent Return-Path: information (the sender can change
+ * as the result of mail forwarding or mailing list delivery), Postfix
+ * removes any existing Return-Path: headers.
+ *
+ * RFC 2821 Section 4.4 specifies: A message-originating SMTP system
+ * SHOULD NOT send a message that already contains a Return-path header.
+ * SMTP servers performing a relay function MUST NOT inspect the message
+ * data, and especially not to the extent needed to determine if
+ * Return-path headers are present. SMTP servers making final delivery
+ * MAY remove Return-path headers before adding their own.
+ */
+ else {
+ state->headers_seen |= (1 << hdr_opts->type);
+ if (hdr_opts->type == HDR_MESSAGE_ID)
+ msg_info("%s: message-id=%s", state->queue_id, hdrval);
+ if (hdr_opts->type == HDR_RESENT_MESSAGE_ID)
+ msg_info("%s: resent-message-id=%s", state->queue_id, hdrval);
+ if (hdr_opts->type == HDR_RECEIVED) {
+ if (state->hop_count >= var_hopcount_limit) {
+ msg_warn("%s: message rejected: hopcount exceeded",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_HOPS;
+ }
+ /* Save our Received: header after maybe updating headers above. */
+ if (state->hop_count == 1)
+ argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END);
+ }
+ if (CLEANUP_OUT_OK(state)) {
+ if (hdr_opts->flags & HDR_OPT_RR)
+ state->resent = "Resent-";
+ if ((hdr_opts->flags & HDR_OPT_SENDER)
+ && state->hdr_rewrite_context) {
+ cleanup_rewrite_sender(state, hdr_opts, header_buf);
+ } else if ((hdr_opts->flags & HDR_OPT_RECIP)
+ && state->hdr_rewrite_context) {
+ cleanup_rewrite_recip(state, hdr_opts, header_buf);
+ } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) {
+ cleanup_out_header(state, header_buf);
+ }
+ }
+ }
+}
+
+/* cleanup_header_done_callback - insert missing message headers */
+
+static void cleanup_header_done_callback(void *context)
+{
+ const char *myname = "cleanup_header_done_callback";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ char time_stamp[1024]; /* XXX locale dependent? */
+ struct tm *tp;
+ TOK822 *token;
+ TOK822 *dummy_token;
+ time_t tv;
+
+ /*
+ * XXX Workaround: when we reach the end of headers, mime_state_update()
+ * may execute up to three call-backs before returning to the caller:
+ * head_out(), head_end(), and body_out() or body_end(). As long as
+ * call-backs don't return a result, each call-back has to check for
+ * itself if the previous call-back experienced a problem.
+ */
+ if (CLEANUP_OUT_OK(state) == 0)
+ return;
+
+ /*
+ * Future proofing: the Milter client's header suppression algorithm
+ * assumes that the MTA prepends its own Received: header. This
+ * assupmtion may be violated after some source-code update. The
+ * following check ensures consistency, at least for local submission.
+ */
+ if (state->hop_count < 1) {
+ msg_warn("%s: message rejected: no Received: header",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_BAD;
+ return;
+ }
+
+ /*
+ * Add a missing (Resent-)Message-Id: header. The message ID gives the
+ * time in GMT units, plus the local queue ID.
+ *
+ * XXX Message-Id is not a required message header (RFC 822 and RFC 2822).
+ *
+ * XXX It is the queue ID non-inode bits that prevent messages from getting
+ * the same Message-Id within the same second.
+ *
+ * XXX An arbitrary amount of time may pass between the start of the mail
+ * transaction and the creation of a queue file. Since we guarantee queue
+ * ID uniqueness only within a second, we must ensure that the time in
+ * the message ID matches the queue ID creation time, as long as we use
+ * the queue ID in the message ID.
+ *
+ * XXX We log a dummy name=value record so that we (hopefully) don't break
+ * compatibility with existing logfile analyzers, and so that we don't
+ * complicate future code that wants to log more name=value attributes.
+ */
+ if ((state->hdr_rewrite_context || var_always_add_hdrs)
+ && (state->headers_seen & (1 << (state->resent[0] ?
+ HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) {
+ if (var_long_queue_ids) {
+ vstring_sprintf(state->temp1, "%s@%s",
+ state->queue_id, var_myhostname);
+ } else {
+ tv = state->handle->ctime.tv_sec;
+ tp = gmtime(&tv);
+ strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp);
+ vstring_sprintf(state->temp1, "%s.%s@%s",
+ time_stamp, state->queue_id, var_myhostname);
+ }
+ cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s>",
+ state->resent, vstring_str(state->temp1));
+ msg_info("%s: %smessage-id=<%s>",
+ state->queue_id, *state->resent ? "resent-" : "",
+ vstring_str(state->temp1));
+ state->headers_seen |= (1 << (state->resent[0] ?
+ HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID));
+ }
+ if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0)
+ msg_info("%s: message-id=<>", state->queue_id);
+
+ /*
+ * Add a missing (Resent-)Date: header. The date is in local time units,
+ * with the GMT offset at the end.
+ */
+ if ((state->hdr_rewrite_context || var_always_add_hdrs)
+ && (state->headers_seen & (1 << (state->resent[0] ?
+ HDR_RESENT_DATE : HDR_DATE))) == 0) {
+ cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s",
+ state->resent, mail_date(state->arrival_time.tv_sec));
+ }
+
+ /*
+ * Add a missing (Resent-)From: header.
+ */
+ if ((state->hdr_rewrite_context || var_always_add_hdrs)
+ && (state->headers_seen & (1 << (state->resent[0] ?
+ HDR_RESENT_FROM : HDR_FROM))) == 0) {
+ quote_822_local(state->temp1, *state->sender ?
+ state->sender : MAIL_ADDR_MAIL_DAEMON);
+ if (*state->sender && state->fullname && *state->fullname) {
+ char *cp;
+
+ /* Enforce some sanity on full name content. */
+ while ((cp = strchr(state->fullname, '\r')) != 0
+ || (cp = strchr(state->fullname, '\n')) != 0)
+ *cp = ' ';
+
+ switch (hfrom_format_code) {
+
+ /*
+ * "From: phrase <route-addr>". Quote the phrase if it
+ * contains specials or the "%!" legacy address operators.
+ */
+ case HFROM_FORMAT_CODE_STD:
+ vstring_sprintf(state->temp2, "%sFrom: ", state->resent);
+ if (state->fullname[strcspn(state->fullname,
+ "%!" LEX_822_SPECIALS)] == 0) {
+ /* Normalize whitespace. */
+ token = tok822_scan_limit(state->fullname, &dummy_token,
+ var_token_limit);
+ } else {
+ token = tok822_alloc(TOK822_QSTRING, state->fullname);
+ }
+ if (token) {
+ tok822_externalize(state->temp2, token, TOK822_STR_NONE);
+ tok822_free(token);
+ vstring_strcat(state->temp2, " ");
+ }
+ vstring_sprintf_append(state->temp2, "<%s>",
+ vstring_str(state->temp1));
+ break;
+
+ /*
+ * "From: addr-spec (ctext)". This is the obsolete form.
+ */
+ case HFROM_FORMAT_CODE_OBS:
+ vstring_sprintf(state->temp2, "%sFrom: %s ",
+ state->resent, vstring_str(state->temp1));
+ vstring_sprintf(state->temp1, "(%s)", state->fullname);
+ token = tok822_parse(vstring_str(state->temp1));
+ tok822_externalize(state->temp2, token, TOK822_STR_NONE);
+ tok822_free_tree(token);
+ break;
+ default:
+ msg_panic("%s: unknown header format %d",
+ myname, hfrom_format_code);
+ }
+ }
+
+ /*
+ * "From: addr-spec". This is the form in the absence of full name
+ * information, also used for mail from mailer-daemon.
+ */
+ else {
+ vstring_sprintf(state->temp2, "%sFrom: %s",
+ state->resent, vstring_str(state->temp1));
+ }
+ CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2);
+ }
+
+ /*
+ * XXX 2821: Appendix B: The return address in the MAIL command SHOULD,
+ * if possible, be derived from the system's identity for the submitting
+ * (local) user, and the "From:" header field otherwise. If there is a
+ * system identity available, it SHOULD also be copied to the Sender
+ * header field if it is different from the address in the From header
+ * field. (Any Sender field that was already there SHOULD be removed.)
+ * Similar wording appears in RFC 2822 section 3.6.2.
+ *
+ * Postfix presently does not insert a Sender: header if envelope and From:
+ * address differ. Older Postfix versions assumed that the envelope
+ * sender address specifies the system identity and inserted Sender:
+ * whenever envelope and From: differed. This was wrong with relayed
+ * mail, and was often not even desirable with original submissions.
+ *
+ * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can
+ * contain multiple addresses. If this is the case, then a Sender: header
+ * must be provided with a single address.
+ *
+ * Postfix does not count the number of addresses in a From: header
+ * (although doing so is trivial, once the address is parsed).
+ */
+
+ /*
+ * Add a missing destination header.
+ */
+#define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \
+ | (1 << HDR_CC) | (1 << HDR_RESENT_CC))
+
+ if ((state->hdr_rewrite_context || var_always_add_hdrs)
+ && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) {
+ if (!is_header(var_rcpt_witheld)) {
+ msg_warn("bad %s header text \"%s\" -- "
+ "need \"headername: headervalue\"",
+ VAR_RCPT_WITHELD, var_rcpt_witheld);
+ } else {
+ cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld);
+ }
+ }
+
+ /*
+ * Place a dummy PTR record right after the last header so that we can
+ * append headers without having to worry about clobbering the
+ * end-of-content marker.
+ */
+ if (state->milters || cleanup_milters) {
+ if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+ state->body_offset = state->append_hdr_pt_target;
+ }
+}
+
+/* cleanup_body_callback - output one body record */
+
+static void cleanup_body_callback(void *context, int type,
+ const char *buf, ssize_t len,
+ off_t offset)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+
+ /*
+ * XXX Workaround: when we reach the end of headers, mime_state_update()
+ * may execute up to three call-backs before returning to the caller:
+ * head_out(), head_end(), and body_out() or body_end(). As long as
+ * call-backs don't return a result, each call-back has to check for
+ * itself if the previous call-back experienced a problem.
+ */
+ if (CLEANUP_OUT_OK(state) == 0)
+ return;
+
+ /*
+ * Crude message body content filter for emergencies. This code has
+ * several problems: it sees one line at a time; it looks at long lines
+ * only in chunks of line_length_limit (2048) characters; it is easily
+ * bypassed with encodings and other tricks.
+ */
+ if ((state->flags & CLEANUP_FLAG_FILTER)
+ && cleanup_body_checks
+ && (var_body_check_len == 0 || offset < var_body_check_len)) {
+ const char *value;
+
+ if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
+ const char *result;
+
+ if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY,
+ buf, value, VAR_BODY_CHECKS))
+ == CLEANUP_ACT_DROP) {
+ return;
+ } else if (result != buf) {
+ cleanup_out(state, type, result, strlen(result));
+ myfree((void *) result);
+ return;
+ }
+ } else if (cleanup_body_checks->error) {
+ msg_warn("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, cleanup_body_checks->title);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+ cleanup_out(state, type, buf, len);
+}
+
+/* cleanup_message_headerbody - process message content, header and body */
+
+static void cleanup_message_headerbody(CLEANUP_STATE *state, int type,
+ const char *buf, ssize_t len)
+{
+ const char *myname = "cleanup_message_headerbody";
+ const MIME_STATE_DETAIL *detail;
+ const char *cp;
+ char *dst;
+
+ /*
+ * Replace each stray CR or LF with one space. These are not allowed in
+ * SMTP, and can be used to enable outbound (remote) SMTP smuggling.
+ * Replacing these early ensures that our later DKIM etc. signature will
+ * not be invalidated. Besides preventing SMTP smuggling, replacing stray
+ * <CR> or <LF> ensures that the result of signature validation by a
+ * later mail system will not depend on how that mail system handles
+ * those stray characters in an implementation-dependent manner.
+ *
+ * The input length is not changed, therefore it is safe to overwrite the
+ * input.
+ */
+ if (var_cleanup_mask_stray_cr_lf)
+ for (dst = (char *) buf; dst < buf + len; dst++)
+ if (*dst == '\r' || *dst == '\n')
+ *dst = ' ';
+
+ /*
+ * Reject unwanted characters.
+ *
+ * XXX Possible optimization: simplify the loop when the "reject" set
+ * contains only one character.
+ */
+ if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) {
+ for (cp = buf; cp < buf + len; cp++) {
+ if (memchr(vstring_str(cleanup_reject_chars),
+ *(const unsigned char *) cp,
+ VSTRING_LEN(cleanup_reject_chars))) {
+ cleanup_act(state, CLEANUP_ACT_CTXT_ANY,
+ buf, "REJECT disallowed character",
+ "character reject");
+ return;
+ }
+ }
+ }
+
+ /*
+ * Strip unwanted characters. Don't overwrite the input.
+ *
+ * XXX Possible space+time optimization: use a bitset.
+ *
+ * XXX Possible optimization: simplify the loop when the "strip" set
+ * contains only one character.
+ *
+ * XXX Possible optimization: copy the input only if we really have to.
+ */
+ if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) {
+ VSTRING_RESET(state->stripped_buf);
+ VSTRING_SPACE(state->stripped_buf, len + 1);
+ dst = vstring_str(state->stripped_buf);
+ for (cp = buf; cp < buf + len; cp++)
+ if (!memchr(vstring_str(cleanup_strip_chars),
+ *(const unsigned char *) cp,
+ VSTRING_LEN(cleanup_strip_chars)))
+ *dst++ = *cp;
+ *dst = 0;
+ buf = vstring_str(state->stripped_buf);
+ len = dst - buf;
+ }
+
+ /*
+ * Copy text record to the output.
+ */
+ if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) {
+ state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
+ }
+
+ /*
+ * If we have reached the end of the message content segment, record the
+ * current file position so we can compute the message size lateron.
+ */
+ else if (type == REC_TYPE_XTRA) {
+ state->mime_errs = mime_state_update(state->mime_state, type, buf, len);
+ if (state->milters || cleanup_milters)
+ /* Make room for body modification. */
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L);
+ /* Ignore header truncation after primary message headers. */
+ state->mime_errs &= ~MIME_ERR_TRUNC_HEADER;
+ if (state->mime_errs && state->reason == 0) {
+ state->errs |= CLEANUP_STAT_CONT;
+ detail = mime_state_detail(state->mime_errs);
+ state->reason = dsn_prepend(detail->dsn, detail->text);
+ }
+ state->mime_state = mime_state_free(state->mime_state);
+ if ((state->xtra_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+ state->cont_length = state->xtra_offset - state->data_offset;
+ state->action = cleanup_extracted;
+ }
+
+ /*
+ * This should never happen.
+ */
+ else {
+ msg_warn("%s: message rejected: "
+ "unexpected record type %d in message content", myname, type);
+ state->errs |= CLEANUP_STAT_BAD;
+ }
+}
+
+/* cleanup_mime_error_callback - error report call-back routine */
+
+static void cleanup_mime_error_callback(void *context, int err_code,
+ const char *text, ssize_t len)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ const char *origin;
+
+ /*
+ * Message header too large errors are handled after the end of the
+ * primary message headers.
+ */
+ if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) {
+ if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0)
+ origin = MAIL_ATTR_ORG_NONE;
+#define TEXT_LEN (len < 100 ? (int) len : 100)
+ msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>",
+ state->queue_id, mime_state_error(err_code), TEXT_LEN, text,
+ origin, info_log_addr_form_sender(state->sender),
+ info_log_addr_form_recipient(state->recip ?
+ state->recip : "unknown"));
+ }
+}
+
+/* cleanup_message - initialize message content segment */
+
+void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len)
+{
+ const char *myname = "cleanup_message";
+ int mime_options;
+
+ /*
+ * Write the start-of-content segment marker.
+ */
+ cleanup_out_string(state, REC_TYPE_MESG, "");
+ if ((state->data_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path);
+
+ /*
+ * Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables
+ * special processing of Content-Type: headers, and thus, causes all text
+ * after the primary headers to be treated as the message body.
+ */
+ mime_options = 0;
+ if (var_disable_mime_input) {
+ mime_options |= MIME_OPT_DISABLE_MIME;
+ } else {
+ /* Turn off content checks if bouncing or forwarding mail. */
+ if (state->flags & CLEANUP_FLAG_FILTER) {
+ if (var_strict_8bitmime || var_strict_7bit_hdrs)
+ mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER;
+ if (var_strict_8bitmime || var_strict_8bit_body)
+ mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY;
+ if (var_strict_encoding)
+ mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN;
+ if (var_strict_8bitmime || var_strict_7bit_hdrs
+ || var_strict_8bit_body || var_strict_encoding
+ || *var_header_checks || *var_mimehdr_checks
+ || *var_nesthdr_checks)
+ mime_options |= MIME_OPT_REPORT_NESTING;
+ }
+ }
+ state->mime_state = mime_state_alloc(mime_options,
+ cleanup_header_callback,
+ cleanup_header_done_callback,
+ cleanup_body_callback,
+ (MIME_STATE_ANY_END) 0,
+ cleanup_mime_error_callback,
+ (void *) state);
+
+ /*
+ * XXX Workaround: truncate a long message header so that we don't exceed
+ * the default Sendmail libmilter request size limit of 65535.
+ */
+#define KLUDGE_HEADER_LIMIT 60000
+ if ((cleanup_milters || state->milters)
+ && var_header_limit > KLUDGE_HEADER_LIMIT)
+ var_header_limit = KLUDGE_HEADER_LIMIT;
+
+ /*
+ * Pass control to the header processing routine.
+ */
+ state->action = cleanup_message_headerbody;
+ cleanup_message_headerbody(state, type, buf, len);
+}
diff --git a/src/cleanup/cleanup_milter.c b/src/cleanup/cleanup_milter.c
new file mode 100644
index 0000000..b6a1ec4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.c
@@ -0,0 +1,2748 @@
+/*++
+/* NAME
+/* cleanup_milter 3
+/* SUMMARY
+/* external mail filter support
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* void cleanup_milter_header_checks_init(void)
+/*
+/* void cleanup_milter_receive(state, count)
+/* CLEANUP_STATE *state;
+/* int count;
+/*
+/* void cleanup_milter_inspect(state, milters)
+/* CLEANUP_STATE *state;
+/* MILTERS *milters;
+/*
+/* cleanup_milter_emul_mail(state, milters, sender)
+/* CLEANUP_STATE *state;
+/* MILTERS *milters;
+/* const char *sender;
+/*
+/* cleanup_milter_emul_rcpt(state, milters, recipient)
+/* CLEANUP_STATE *state;
+/* MILTERS *milters;
+/* const char *recipient;
+/*
+/* cleanup_milter_emul_data(state, milters)
+/* CLEANUP_STATE *state;
+/* MILTERS *milters;
+/* DESCRIPTION
+/* This module implements support for Sendmail-style mail
+/* filter (milter) applications, including in-place queue file
+/* modification.
+/*
+/* cleanup_milter_header_checks_init() does pre-jail
+/* initializations.
+/*
+/* cleanup_milter_receive() receives mail filter definitions,
+/* typically from an smtpd(8) server process, and registers
+/* local call-back functions for macro expansion and for queue
+/* file modification.
+/*
+/* cleanup_milter_inspect() sends the current message headers
+/* and body to the mail filters that were received with
+/* cleanup_milter_receive(), or that are specified with the
+/* cleanup_milters configuration parameter.
+/*
+/* cleanup_milter_emul_mail() emulates connect, helo and mail
+/* events for mail that does not arrive via the smtpd(8) server.
+/* The emulation pretends that mail arrives from localhost/127.0.0.1
+/* via ESMTP. Milters can reject emulated connect, helo, mail
+/* or data events, but not emulated rcpt events as described
+/* next.
+/*
+/* cleanup_milter_emul_rcpt() emulates an rcpt event for mail
+/* that does not arrive via the smtpd(8) server. This reports
+/* a server configuration error condition when the milter
+/* rejects an emulated rcpt event.
+/*
+/* cleanup_milter_emul_data() emulates a data event for mail
+/* that does not arrive via the smtpd(8) server. It's OK for
+/* milters to reject emulated data events.
+/* SEE ALSO
+/* milter(3) generic mail filter interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Panic: interface violation.
+/* Warnings: I/O errors (state->errs is updated accordingly).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h> /* AF_INET */
+#include <string.h>
+#include <errno.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <off_cvt.h>
+#include <dsn_mask.h>
+#include <rec_type.h>
+#include <cleanup_user.h>
+#include <record.h>
+#include <rec_attr_map.h>
+#include <mail_proto.h>
+#include <mail_params.h>
+#include <lex_822.h>
+#include <is_header.h>
+#include <quote_821_local.h>
+#include <dsn_util.h>
+#include <xtext.h>
+#include <info_log_addr_form.h>
+
+/* Application-specific. */
+
+#include <cleanup.h>
+
+ /*
+ * How Postfix 2.4 edits queue file information:
+ *
+ * Mail filter applications (Milters) can send modification requests after
+ * receiving the end of the message body. Postfix implements these
+ * modifications in the cleanup server, so that it can edit the queue file
+ * in place. This avoids the temporary files that would be needed when
+ * modifications were implemented in the SMTP server (Postfix normally does
+ * not store the whole message in main memory). Once a Milter is done
+ * editing, the queue file can be used as input for the next Milter, and so
+ * on. Finally, the cleanup server changes file permissions, calls fsync(),
+ * and waits for successful completion.
+ *
+ * To implement in-place queue file edits, we need to introduce surprisingly
+ * little change to the existing Postfix queue file structure. All we need
+ * is a way to mark a record as deleted, and to jump from one place in the
+ * queue file to another. We could implement deleted records with jumps, but
+ * marking is sometimes simpler.
+ *
+ * Postfix does not store queue files as plain text files. Instead all
+ * information is stored in records with an explicit type and length, for
+ * sender, recipient, arrival time, and so on. Even the content that makes
+ * up the message header and body is stored as records with explicit types
+ * and lengths. This organization makes it very easy to mark a record as
+ * deleted, and to introduce the pointer records that we will use to jump
+ * from one place in a queue file to another place.
+ *
+ * - Deleting a recipient is easiest - simply modify the record type into one
+ * that is skipped by the software that delivers mail. We won't try to reuse
+ * the deleted recipient for other purposes. When deleting a recipient, we
+ * may need to delete multiple recipient records that result from virtual
+ * alias expansion of the original recipient address.
+ *
+ * - Replacing a header record involves pointer records. A record is replaced
+ * by overwriting it with a forward pointer to space after the end of the
+ * queue file, putting the new record there, followed by a reverse pointer
+ * to the record that follows the replaced header. To simplify
+ * implementation we follow a short header record with a filler record so
+ * that we can always overwrite a header record with a pointer.
+ *
+ * N.B. This is a major difference with Postfix version 2.3, which needed
+ * complex code to save records that follow a short header, before it could
+ * overwrite a short header record. This code contained two of the three
+ * post-release bugs that were found with Postfix header editing.
+ *
+ * - Inserting a header record is like replacing one, except that we also
+ * relocate the record that is being overwritten by the forward pointer.
+ *
+ * - Deleting a message header is simplest when we replace it by a "skip"
+ * pointer to the information that follows the header. With a multi-line
+ * header we need to update only the first line.
+ *
+ * - Appending a recipient or header record involves pointer records as well.
+ * To make this convenient, the queue file already contains dummy pointer
+ * records at the locations where we want to append recipient or header
+ * content. To append, change the dummy pointer into a forward pointer to
+ * space after the end of a message, put the new recipient or header record
+ * there, followed by a reverse pointer to the record that follows the
+ * forward pointer.
+ *
+ * - To append another header or recipient record, replace the reverse pointer
+ * by a forward pointer to space after the end of a message, put the new
+ * record there, followed by the value of the reverse pointer that we
+ * replace. Thus, there is no one-to-one correspondence between forward and
+ * backward pointers. Instead, there can be multiple forward pointers for
+ * one reverse pointer.
+ *
+ * - When a mail filter wants to replace an entire body, we overwrite existing
+ * body records until we run out of space, and then write a pointer to space
+ * after the end of the queue file, followed by more body content. There may
+ * be multiple regions with body content; regions are connected by forward
+ * pointers, and the last region ends with a pointer to the marker that ends
+ * the message content segment. Body regions can be large and therefore they
+ * are reused to avoid wasting space. Sendmail mail filters currently do not
+ * replace individual body records, and that is a good thing.
+ *
+ * Making queue file modifications safe:
+ *
+ * Postfix queue files are segmented. The first segment is for envelope
+ * records, the second for message header and body content, and the third
+ * segment is for information that was extracted or generated from the
+ * message header or body content. Each segment is terminated by a marker
+ * record. For now we don't want to change their location. That is, we want
+ * to avoid moving the records that mark the start or end of a queue file
+ * segment.
+ *
+ * To ensure that we can always replace a header or body record by a pointer
+ * record, without having to relocate a marker record, the cleanup server
+ * places a dummy pointer record at the end of the recipients and at the end
+ * of the message header. To support message body modifications, a dummy
+ * pointer record is also placed at the end of the message content.
+ *
+ * With all these changes in queue file organization, REC_TYPE_END is no longer
+ * guaranteed to be the last record in a queue file. If an application were
+ * to read beyond the REC_TYPE_END marker, it would go into an infinite
+ * loop, because records after REC_TYPE_END alternate with reverse pointers
+ * to the middle of the queue file. For robustness, the record reading
+ * routine skips forward to the end-of-file position after reading the
+ * REC_TYPE_END marker.
+ */
+
+/*#define msg_verbose 2*/
+
+static HBC_CHECKS *cleanup_milter_hbc_checks;
+static VSTRING *cleanup_milter_hbc_reply;
+static void cleanup_milter_set_error(CLEANUP_STATE *, int);
+static const char *cleanup_add_rcpt_par(void *, const char *, const char *);
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* cleanup_milter_hbc_log - log post-milter header/body_checks action */
+
+static void cleanup_milter_hbc_log(void *context, const char *action,
+ const char *where, const char *line,
+ const char *optional_text)
+{
+ const CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ const char *attr;
+
+ vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.60s from %s[%s];",
+ state->queue_id, where, action, where, line,
+ state->client_name, state->client_addr);
+ if (state->sender)
+ vstring_sprintf_append(state->temp1, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recip)
+ vstring_sprintf_append(state->temp1, " to=<%s>",
+ info_log_addr_form_recipient(state->recip));
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " proto=%s", attr);
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
+ if (optional_text)
+ vstring_sprintf_append(state->temp1, ": %s", optional_text);
+ msg_info("%s", vstring_str(state->temp1));
+}
+
+/* cleanup_milter_header_prepend - prepend header to milter-generated header */
+
+static void cleanup_milter_header_prepend(void *context, int rec_type,
+ const char *buf, ssize_t len, off_t offset)
+{
+ /* XXX save prepended header to buffer. */
+ msg_warn("the milter_header/body_checks prepend action is not implemented");
+}
+
+/* cleanup_milter_hbc_extend - additional header/body_checks actions */
+
+static char *cleanup_milter_hbc_extend(void *context, const char *command,
+ ssize_t cmd_len, const char *optional_text,
+ const char *where, const char *buf,
+ ssize_t buf_len, off_t offset)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ const char *map_class = VAR_MILT_HEAD_CHECKS; /* XXX */
+
+#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
+
+ /*
+ * These are currently our mutually-exclusive ways of not receiving mail:
+ * "reject" and "discard". Only these can be reported to the up-stream
+ * Postfix libmilter code, because sending any reply there causes Postfix
+ * libmilter to skip further "edit" requests. By way of safety net, each
+ * of these must also reset CLEANUP_FLAG_FILTER_ALL.
+ */
+#define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \
+ ((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT))
+
+ /*
+ * We log all header/body-checks actions here, because we know the
+ * details of the message content that triggered the action. We report
+ * detail-free milter-reply values (reject/discard, stored in the
+ * milter_hbc_reply state member) to the Postfix libmilter code, so that
+ * Postfix libmilter can stop sending requests.
+ *
+ * We also set all applicable cleanup flags here, because there is no
+ * guarantee that Postfix libmilter will propagate our own milter-reply
+ * value to cleanup_milter_inspect() which calls cleanup_milter_apply().
+ * The latter translates responses from Milter applications into cleanup
+ * flags, and logs the response text. Postfix libmilter can convey only
+ * one milter-reply value per email message, and that reply may even come
+ * from outside Postfix.
+ *
+ * To suppress redundant logging, cleanup_milter_apply() does nothing when
+ * the milter-reply value matches the saved text in the milter_hbc_reply
+ * state member. As we remember only one milter-reply value, we can't
+ * report multiple milter-reply values per email message. We satisfy this
+ * constraint, because we already clear the CLEANUP_FLAG_FILTER_ALL flags
+ * to terminate further header inspection.
+ */
+ if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0)
+ return ((char *) buf);
+
+ if (STREQUAL(command, "BCC", cmd_len)) {
+ if (strchr(optional_text, '@') == 0) {
+ msg_warn("bad BCC address \"%s\" in %s map -- "
+ "need user@domain",
+ optional_text, VAR_MILT_HEAD_CHECKS);
+ } else {
+ cleanup_milter_hbc_log(context, "bcc", where, buf, optional_text);
+ /* Caller checks state error flags. */
+ (void) cleanup_add_rcpt_par(state, optional_text, "");
+ }
+ return ((char *) buf);
+ }
+ if (STREQUAL(command, "REJECT", cmd_len)) {
+ const CLEANUP_STAT_DETAIL *detail;
+
+ if (state->reason)
+ myfree(state->reason);
+ detail = cleanup_stat_detail(CLEANUP_STAT_CONT);
+ if (*optional_text) {
+ state->reason = dsn_prepend(detail->dsn, optional_text);
+ if (*state->reason != '4' && *state->reason != '5') {
+ msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x",
+ optional_text);
+ *state->reason = '4';
+ }
+ } else {
+ state->reason = dsn_prepend(detail->dsn, detail->text);
+ }
+ if (*state->reason == '4')
+ state->errs |= CLEANUP_STAT_DEFER;
+ else
+ state->errs |= CLEANUP_STAT_CONT;
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ cleanup_milter_hbc_log(context, "reject", where, buf, state->reason);
+ vstring_sprintf(cleanup_milter_hbc_reply, "%d %s",
+ detail->smtp, state->reason);
+ STR(cleanup_milter_hbc_reply)[0] = *state->reason;
+ return ((char *) buf);
+ }
+ if (STREQUAL(command, "FILTER", cmd_len)) {
+ if (*optional_text == 0) {
+ msg_warn("missing FILTER command argument in %s map", map_class);
+ } else if (strchr(optional_text, ':') == 0) {
+ msg_warn("bad FILTER command %s in %s -- "
+ "need transport:destination",
+ optional_text, map_class);
+ } else {
+ if (state->filter)
+ myfree(state->filter);
+ state->filter = mystrdup(optional_text);
+ cleanup_milter_hbc_log(context, "filter", where, buf,
+ optional_text);
+ }
+ return ((char *) buf);
+ }
+ if (STREQUAL(command, "DISCARD", cmd_len)) {
+ cleanup_milter_hbc_log(context, "discard", where, buf, optional_text);
+ vstring_strcpy(cleanup_milter_hbc_reply, "D");
+ state->flags |= CLEANUP_FLAG_DISCARD;
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ return ((char *) buf);
+ }
+ if (STREQUAL(command, "HOLD", cmd_len)) {
+ if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) {
+ cleanup_milter_hbc_log(context, "hold", where, buf, optional_text);
+ state->flags |= CLEANUP_FLAG_HOLD;
+ }
+ return ((char *) buf);
+ }
+ if (STREQUAL(command, "REDIRECT", cmd_len)) {
+ if (strchr(optional_text, '@') == 0) {
+ msg_warn("bad REDIRECT target \"%s\" in %s map -- "
+ "need user@domain",
+ optional_text, map_class);
+ } else {
+ if (state->redirect)
+ myfree(state->redirect);
+ state->redirect = mystrdup(optional_text);
+ cleanup_milter_hbc_log(context, "redirect", where, buf,
+ optional_text);
+ state->flags &= ~CLEANUP_FLAG_FILTER_ALL;
+ }
+ return ((char *) buf);
+ }
+ return ((char *) HBC_CHECKS_STAT_UNKNOWN);
+}
+
+/* cleanup_milter_header_checks - inspect Milter-generated header */
+
+static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf)
+{
+ char *ret;
+
+ /*
+ * Milter application "add/insert/replace header" requests happen at the
+ * end-of-message stage, therefore all the header operations are relative
+ * to the primary message header.
+ */
+ ret = hbc_header_checks((void *) state, cleanup_milter_hbc_checks,
+ MIME_HDR_PRIMARY, (HEADER_OPTS *) 0,
+ buf, (off_t) 0);
+ if (ret == 0) {
+ return (0);
+ } else if (ret == HBC_CHECKS_STAT_ERROR) {
+ msg_warn("%s: %s map lookup problem -- "
+ "message not accepted, try again later",
+ state->queue_id, VAR_MILT_HEAD_CHECKS);
+ state->errs |= CLEANUP_STAT_WRITE;
+ return (0);
+ } else {
+ if (ret != STR(buf)) {
+ vstring_strcpy(buf, ret);
+ myfree(ret);
+ }
+ return (1);
+ }
+}
+
+/* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */
+
+static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state)
+{
+ const char *myname = "cleanup_milter_hbc_add_meta_records";
+ off_t reverse_ptr_offset;
+ off_t new_meta_offset;
+
+ /*
+ * Note: this code runs while the Milter infrastructure is being torn
+ * down. For this reason we handle all I/O errors here on the spot,
+ * instead of reporting them back through the Milter infrastructure.
+ */
+
+ /*
+ * Sanity check.
+ */
+ if (state->append_meta_pt_offset < 0)
+ msg_panic("%s: no meta append pointer location", myname);
+ if (state->append_meta_pt_target < 0)
+ msg_panic("%s: no meta append pointer target", myname);
+
+ /*
+ * Allocate space after the end of the queue file, and write the meta
+ * record(s), followed by a reverse pointer record that points to the
+ * target of the old "meta record append" pointer record. This reverse
+ * pointer record becomes the new "meta record append" pointer record.
+ * Although the new "meta record append" pointer record will never be
+ * used, we update it here to make the code more similar to other code
+ * that inserts/appends content, so that common code can be factored out
+ * later.
+ */
+ if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ cleanup_milter_set_error(state, errno);
+ return;
+ }
+ if (state->filter != 0)
+ cleanup_out_string(state, REC_TYPE_FILT, state->filter);
+ if (state->redirect != 0)
+ cleanup_out_string(state, REC_TYPE_RDR, state->redirect);
+ if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ state->errs |= CLEANUP_STAT_WRITE;
+ return;
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->append_meta_pt_target);
+
+ /*
+ * Pointer flipping: update the old "meta record append" pointer record
+ * value with the location of the new meta record.
+ */
+ if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) {
+ cleanup_milter_set_error(state, errno);
+ return;
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) new_meta_offset);
+
+ /*
+ * Update the in-memory "meta append" pointer record location with the
+ * location of the reverse pointer record that follows the new meta
+ * record. The target of the "meta append" pointer record does not
+ * change; it's always the record that follows the dummy pointer record
+ * that was written while Postfix received the message.
+ */
+ state->append_meta_pt_offset = reverse_ptr_offset;
+
+ /*
+ * Note: state->append_meta_pt_target never changes.
+ */
+}
+
+/* cleanup_milter_header_checks_init - initialize post-Milter header checks */
+
+void cleanup_milter_header_checks_init(void)
+{
+ static const char myname[] = "cleanup_milter_header_checks_init";
+
+#define NO_NESTED_HDR_NAME ""
+#define NO_NESTED_HDR_VALUE ""
+#define NO_MIME_HDR_NAME ""
+#define NO_MIME_HDR_VALUE ""
+
+ static /* XXX not const */ HBC_CALL_BACKS call_backs = {
+ cleanup_milter_hbc_log,
+ cleanup_milter_header_prepend,
+ cleanup_milter_hbc_extend,
+ };
+
+ if (*var_milt_head_checks == 0)
+ msg_panic("%s: %s is empty", myname, VAR_MILT_HEAD_CHECKS);
+
+ if (cleanup_milter_hbc_checks)
+ msg_panic("%s: cleanup_milter_hbc_checks is not null", myname);
+ cleanup_milter_hbc_checks =
+ hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks,
+ NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE,
+ NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE,
+ &call_backs);
+
+ if (cleanup_milter_hbc_reply)
+ msg_panic("%s: cleanup_milter_hbc_reply is not null", myname);
+ cleanup_milter_hbc_reply = vstring_alloc(100);
+}
+
+#ifdef TEST
+
+/* cleanup_milter_header_checks_deinit - undo cleanup_milter_header_checks_init */
+
+static void cleanup_milter_header_checks_deinit(void)
+{
+ static const char myname[] = "cleanup_milter_header_checks_deinit";
+
+ if (cleanup_milter_hbc_checks == 0)
+ msg_panic("%s: cleanup_milter_hbc_checks is null", myname);
+ hbc_header_checks_free(cleanup_milter_hbc_checks);
+ cleanup_milter_hbc_checks = 0;
+
+ if (cleanup_milter_hbc_reply == 0)
+ msg_panic("%s: cleanup_milter_hbc_reply is null", myname);
+ vstring_free(cleanup_milter_hbc_reply);
+ cleanup_milter_hbc_reply = 0;
+}
+
+#endif
+
+/* cleanup_milter_header_checks_reinit - re-init post-Milter header checks */
+
+static void cleanup_milter_header_checks_reinit(CLEANUP_STATE *state)
+{
+ if (state->filter)
+ myfree(state->filter);
+ state->filter = 0;
+ if (state->redirect)
+ myfree(state->redirect);
+ state->redirect = 0;
+ VSTRING_RESET(cleanup_milter_hbc_reply);
+}
+
+/* cleanup_milter_hbc_finish - finalize post-Milter header checks */
+
+static void cleanup_milter_hbc_finish(CLEANUP_STATE *state)
+{
+ if (CLEANUP_OUT_OK(state)
+ && !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)
+ && (state->filter || state->redirect))
+ cleanup_milter_hbc_add_meta_records(state);
+}
+
+ /*
+ * Milter replies.
+ */
+#define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \
+ if ((__state)->reason) \
+ myfree((__state)->reason); \
+ (__state)->reason = mystrdup(__reason); \
+ if ((__state)->smtp_reply) { \
+ myfree((__state)->smtp_reply); \
+ (__state)->smtp_reply = 0; \
+ } \
+ } while (0)
+
+#define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \
+ if ((__state)->reason) \
+ myfree((__state)->reason); \
+ (__state)->reason = mystrdup(__smtp_reply + 4); \
+ printable((__state)->reason, '_'); \
+ if ((__state)->smtp_reply) \
+ myfree((__state)->smtp_reply); \
+ (__state)->smtp_reply = mystrdup(__smtp_reply); \
+ } while (0)
+
+/* cleanup_milter_set_error - set error flag from errno */
+
+static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
+{
+ if (err == EFBIG) {
+ msg_warn("%s: queue file size limit exceeded", state->queue_id);
+ state->errs |= CLEANUP_STAT_SIZE;
+ } else {
+ msg_warn("%s: write queue file: %m", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+}
+
+/* cleanup_milter_error - return dummy error description */
+
+static const char *cleanup_milter_error(CLEANUP_STATE *state, int err)
+{
+ const char *myname = "cleanup_milter_error";
+ const CLEANUP_STAT_DETAIL *dp;
+
+ /*
+ * For consistency with error reporting within the milter infrastructure,
+ * content manipulation routines return a null pointer on success, and an
+ * SMTP-like response on error.
+ *
+ * However, when cleanup_milter_apply() receives this error response from
+ * the milter infrastructure, it ignores the text since the appropriate
+ * cleanup error flags were already set by cleanup_milter_set_error().
+ *
+ * Specify a null error number when the "errno to error flag" mapping was
+ * already done elsewhere, possibly outside this module.
+ */
+ if (err)
+ cleanup_milter_set_error(state, err);
+ else if (CLEANUP_OUT_OK(state))
+ msg_panic("%s: missing errno to error flag mapping", myname);
+ if (state->milter_err_text == 0)
+ state->milter_err_text = vstring_alloc(50);
+ dp = cleanup_stat_detail(state->errs);
+ return (STR(vstring_sprintf(state->milter_err_text,
+ "%d %s %s", dp->smtp, dp->dsn, dp->text)));
+}
+
+/* cleanup_add_header - append message header */
+
+static const char *cleanup_add_header(void *context, const char *name,
+ const char *space,
+ const char *value)
+{
+ const char *myname = "cleanup_add_header";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ VSTRING *buf;
+ off_t reverse_ptr_offset;
+ off_t new_hdr_offset;
+
+ /*
+ * To simplify implementation, the cleanup server writes a dummy "header
+ * append" pointer record after the last message header. We cache both
+ * the location and the target of the current "header append" pointer
+ * record.
+ */
+ if (state->append_hdr_pt_offset < 0)
+ msg_panic("%s: no header append pointer location", myname);
+ if (state->append_hdr_pt_target < 0)
+ msg_panic("%s: no header append pointer target", myname);
+
+ /*
+ * Return early when Milter header checks request that this header record
+ * be dropped, or that the message is discarded. Note: CLEANUP_OUT_OK()
+ * tests CLEANUP_FLAG_DISCARD. We don't want to report the latter as an
+ * error.
+ */
+ buf = vstring_alloc(100);
+ vstring_sprintf(buf, "%s:%s%s", name, space, value);
+ if (cleanup_milter_hbc_checks) {
+ if (cleanup_milter_header_checks(state, buf) == 0
+ || (state->flags & CLEANUP_FLAG_DISCARD)) {
+ vstring_free(buf);
+ return (0);
+ }
+ if (CLEANUP_OUT_OK(state) == 0) {
+ vstring_free(buf);
+ return (cleanup_milter_error(state, 0));
+ }
+ }
+
+ /*
+ * Allocate space after the end of the queue file, and write the header
+ * record(s), followed by a reverse pointer record that points to the
+ * target of the old "header append" pointer record. This reverse pointer
+ * record becomes the new "header append" pointer record.
+ */
+ if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ vstring_free(buf);
+ return (cleanup_milter_error(state, errno));
+ }
+ /* XXX emit prepended header, then clear it. */
+ cleanup_out_header(state, buf); /* Includes padding */
+ vstring_free(buf);
+ if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->append_hdr_pt_target);
+
+ /*
+ * Pointer flipping: update the old "header append" pointer record value
+ * with the location of the new header record.
+ *
+ * XXX To avoid unnecessary seek operations when the new header immediately
+ * follows the old append header pointer, write a null pointer or make
+ * the record reading loop smarter. Making vstream_fseek() smarter does
+ * not help, because it doesn't know if we're going to read or write
+ * after a write+seek sequence.
+ */
+ if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) new_hdr_offset);
+
+ /*
+ * Update the in-memory "header append" pointer record location with the
+ * location of the reverse pointer record that follows the new header.
+ * The target of the "header append" pointer record does not change; it's
+ * always the record that follows the dummy pointer record that was
+ * written while Postfix received the message.
+ */
+ state->append_hdr_pt_offset = reverse_ptr_offset;
+
+ /*
+ * In case of error while doing record output.
+ */
+ return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
+ cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
+ STR(cleanup_milter_hbc_reply) : 0);
+
+ /*
+ * Note: state->append_hdr_pt_target never changes.
+ */
+}
+
+/* cleanup_find_header_start - find specific header instance */
+
+static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index,
+ const char *header_label,
+ VSTRING *buf,
+ int *prec_type,
+ int allow_ptr_backup,
+ int skip_headers)
+{
+ const char *myname = "cleanup_find_header_start";
+ off_t curr_offset; /* offset after found record */
+ off_t ptr_offset; /* pointer to found record */
+ VSTRING *ptr_buf = 0;
+ int rec_type = REC_TYPE_ERROR;
+ int last_type;
+ ssize_t len;
+ int hdr_count = 0;
+
+ if (msg_verbose)
+ msg_info("%s: index %ld name \"%s\"",
+ myname, (long) index, header_label ? header_label : "(none)");
+
+ /*
+ * Sanity checks.
+ */
+ if (index < 1)
+ msg_panic("%s: bad header index %ld", myname, (long) index);
+
+ /*
+ * Skip to the start of the message content, and read records until we
+ * either find the specified header, or until we hit the end of the
+ * headers.
+ *
+ * The index specifies the header instance: 1 is the first one. The header
+ * label specifies the header name. A null pointer matches any header.
+ *
+ * When the specified header is not found, the result value is -1.
+ *
+ * When the specified header is found, its first record is stored in the
+ * caller-provided read buffer, and the result value is the queue file
+ * offset of that record. The file read position is left at the start of
+ * the next (non-filler) queue file record, which can be the remainder of
+ * a multi-record header.
+ *
+ * When a header is found and allow_ptr_backup is non-zero, then the result
+ * is either the first record of that header, or it is the pointer record
+ * that points to the first record of that header. In the latter case,
+ * the file read position is undefined. Returning the pointer allows us
+ * to do some optimizations when inserting text multiple times at the
+ * same place.
+ *
+ * XXX We can't use the MIME processor here. It not only buffers up the
+ * input, it also reads the record that follows a complete header before
+ * it invokes the header call-back action. This complicates the way that
+ * we discover header offsets and boundaries. Worse is that the MIME
+ * processor is unaware that multi-record message headers can have PTR
+ * records in the middle.
+ *
+ * XXX The draw-back of not using the MIME processor is that we have to
+ * duplicate some of its logic here and in the routine that finds the end
+ * of the header record. To minimize the duplication we define an ugly
+ * macro that is used in all code that scans for header boundaries.
+ *
+ * XXX Sendmail compatibility (based on Sendmail 8.13.6 measurements).
+ *
+ * - When changing Received: header #1, we change the Received: header that
+ * follows our own one; a request to change Received: header #0 is
+ * silently treated as a request to change Received: header #1.
+ *
+ * - When changing Date: header #1, we change the first Date: header; a
+ * request to change Date: header #0 is silently treated as a request to
+ * change Date: header #1.
+ *
+ * Thus, header change requests are relative to the content as received,
+ * that is, the content after our own Received: header. They can affect
+ * only the headers that the MTA actually exposes to mail filter
+ * applications.
+ *
+ * - However, when inserting a header at position 0, the new header appears
+ * before our own Received: header, and when inserting at position 1, the
+ * new header appears after our own Received: header.
+ *
+ * Thus, header insert operations are relative to the content as delivered,
+ * that is, the content including our own Received: header.
+ *
+ * None of the above is applicable after a Milter inserts a header before
+ * our own Received: header. From then on, our own Received: header
+ * becomes just like other headers.
+ */
+#define CLEANUP_FIND_HEADER_NOTFOUND (-1)
+#define CLEANUP_FIND_HEADER_IOERROR (-2)
+
+#define CLEANUP_FIND_HEADER_RETURN(offs) do { \
+ if (ptr_buf) \
+ vstring_free(ptr_buf); \
+ return (offs); \
+ } while (0)
+
+#define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \
+ if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \
+ msg_warn("%s: read file %s: %m", myname, cleanup_path); \
+ cleanup_milter_set_error(state, errno); \
+ do { quit; } while (0); \
+ } \
+ if (msg_verbose > 1) \
+ msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \
+ LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \
+ if (rec_type == REC_TYPE_DTXT) \
+ continue; \
+ if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \
+ && rec_type != REC_TYPE_PTR) \
+ break;
+ /* End of hairy macros. */
+
+ if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ cleanup_milter_set_error(state, errno);
+ CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
+ }
+ for (ptr_offset = 0, last_type = 0; /* void */ ; /* void */ ) {
+ if ((curr_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ cleanup_milter_set_error(state, errno);
+ CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
+ }
+ /* Don't follow the "append header" pointer. */
+ if (curr_offset == state->append_hdr_pt_offset)
+ break;
+ /* Caution: this macro terminates the loop at end-of-message. */
+ /* Don't do complex processing while breaking out of this loop. */
+ GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset,
+ CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR));
+ /* Caution: don't assume ptr->header. This may be header-ptr->body. */
+ if (rec_type == REC_TYPE_PTR) {
+ if (rec_goto(state->dst, STR(buf)) < 0) {
+ msg_warn("%s: read file %s: %m", myname, cleanup_path);
+ cleanup_milter_set_error(state, errno);
+ CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
+ }
+ /* Save PTR record, in case it points to the start of a header. */
+ if (allow_ptr_backup) {
+ ptr_offset = curr_offset;
+ if (ptr_buf == 0)
+ ptr_buf = vstring_alloc(100);
+ vstring_strcpy(ptr_buf, STR(buf));
+ }
+ /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */
+ continue;
+ }
+ /* The middle of a multi-record header. */
+ else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0])) {
+ /* Reset the saved PTR record and update last_type. */
+ }
+ /* No more message headers. */
+ else if ((len = is_header(STR(buf))) == 0) {
+ break;
+ }
+ /* This the start of a message header. */
+ else if (hdr_count++ < skip_headers)
+ /* Reset the saved PTR record and update last_type. */ ;
+ else if ((header_label == 0
+ || (strncasecmp(header_label, STR(buf), len) == 0
+ && (strlen(header_label) == len)))
+ && --index == 0) {
+ /* If we have a saved PTR record, it points to start of header. */
+ break;
+ }
+ ptr_offset = 0;
+ last_type = rec_type;
+ }
+
+ /*
+ * In case of failure, return negative start position.
+ */
+ if (index > 0) {
+ curr_offset = CLEANUP_FIND_HEADER_NOTFOUND;
+ } else {
+
+ /*
+ * Skip over short-header padding, so that the file read pointer is
+ * always positioned at the first non-padding record after the header
+ * record. Insist on padding after short a header record, so that a
+ * short header record can safely be overwritten by a pointer record.
+ */
+ if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) {
+ VSTRING *rbuf = (ptr_offset ? buf :
+ (ptr_buf ? ptr_buf :
+ (ptr_buf = vstring_alloc(100))));
+ int rval;
+
+ if ((rval = rec_get_raw(state->dst, rbuf, 0, REC_FLAG_NONE)) < 0) {
+ cleanup_milter_set_error(state, errno);
+ CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR);
+ }
+ if (rval != REC_TYPE_DTXT)
+ msg_panic("%s: short header without padding", myname);
+ }
+
+ /*
+ * Optionally return a pointer to the message header, instead of the
+ * start of the message header itself. In that case the file read
+ * position is undefined (actually it is at the first non-padding
+ * record that follows the message header record).
+ */
+ if (ptr_offset != 0) {
+ rec_type = REC_TYPE_PTR;
+ curr_offset = ptr_offset;
+ vstring_strcpy(buf, STR(ptr_buf));
+ }
+ *prec_type = rec_type;
+ }
+ if (msg_verbose)
+ msg_info("%s: index %ld name %s type %d offset %ld",
+ myname, (long) index, header_label ?
+ header_label : "(none)", rec_type, (long) curr_offset);
+
+ CLEANUP_FIND_HEADER_RETURN(curr_offset);
+}
+
+/* cleanup_find_header_end - find end of header */
+
+static off_t cleanup_find_header_end(CLEANUP_STATE *state,
+ VSTRING *rec_buf,
+ int last_type)
+{
+ const char *myname = "cleanup_find_header_end";
+ off_t read_offset;
+ int rec_type;
+
+ /*
+ * This routine is called immediately after cleanup_find_header_start().
+ * rec_buf is the cleanup_find_header_start() result record; last_type is
+ * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file
+ * read position is at the first non-padding record after the result
+ * header record.
+ */
+ for (;;) {
+ if ((read_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: read file %s: %m", myname, cleanup_path);
+ cleanup_milter_error(state, errno);
+ return (-1);
+ }
+ /* Don't follow the "append header" pointer. */
+ if (read_offset == state->append_hdr_pt_offset)
+ break;
+ /* Caution: this macro terminates the loop at end-of-message. */
+ /* Don't do complex processing while breaking out of this loop. */
+ GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset,
+ /* Warning and errno->error mapping are done elsewhere. */
+ return (-1));
+ if (rec_type == REC_TYPE_PTR) {
+ if (rec_goto(state->dst, STR(rec_buf)) < 0) {
+ msg_warn("%s: read file %s: %m", myname, cleanup_path);
+ cleanup_milter_error(state, errno);
+ return (-1);
+ }
+ /* Don't update last_type; PTR may follow REC_TYPE_CONT. */
+ continue;
+ }
+ /* Start of header or message body. */
+ if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0]))
+ break;
+ last_type = rec_type;
+ }
+ return (read_offset);
+}
+
+/* cleanup_patch_header - patch new header into an existing header */
+
+static const char *cleanup_patch_header(CLEANUP_STATE *state,
+ const char *new_hdr_name,
+ const char *hdr_space,
+ const char *new_hdr_value,
+ off_t old_rec_offset,
+ int old_rec_type,
+ VSTRING *old_rec_buf,
+ off_t next_offset)
+{
+ const char *myname = "cleanup_patch_header";
+ VSTRING *buf = vstring_alloc(100);
+ off_t new_hdr_offset;
+
+#define CLEANUP_PATCH_HEADER_RETURN(ret) do { \
+ vstring_free(buf); \
+ return (ret); \
+ } while (0)
+
+ if (msg_verbose)
+ msg_info("%s: \"%s\" \"%s\" at %ld",
+ myname, new_hdr_name, new_hdr_value, (long) old_rec_offset);
+
+ /*
+ * Allocate space after the end of the queue file for the new header and
+ * optionally save an existing record to make room for a forward pointer
+ * record. If the saved record was not a PTR record, follow the saved
+ * record by a reverse pointer record that points to the record after the
+ * original location of the saved record.
+ *
+ * We update the queue file in a safe manner: save the new header and the
+ * existing records after the end of the queue file, write the reverse
+ * pointer, and only then overwrite the saved records with the forward
+ * pointer to the new header.
+ *
+ * old_rec_offset, old_rec_type, and old_rec_buf specify the record that we
+ * are about to overwrite with a pointer record. If the record needs to
+ * be saved (i.e. old_rec_type > 0), the buffer contains the data content
+ * of exactly one PTR or text record.
+ *
+ * next_offset specifies the record that follows the to-be-overwritten
+ * record. It is ignored when the to-be-saved record is a pointer record.
+ */
+
+ /*
+ * Return early when Milter header checks request that this header record
+ * be dropped.
+ */
+ vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value);
+ if (cleanup_milter_hbc_checks
+ && cleanup_milter_header_checks(state, buf) == 0)
+ CLEANUP_PATCH_HEADER_RETURN(0);
+
+ /*
+ * Write the new header to a new location after the end of the queue
+ * file.
+ */
+ if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
+ }
+ /* XXX emit prepended header, then clear it. */
+ cleanup_out_header(state, buf); /* Includes padding */
+ if (msg_verbose > 1)
+ msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset,
+ LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf));
+
+ /*
+ * Optionally, save the existing text record or pointer record that will
+ * be overwritten with the forward pointer. Pad a short saved record to
+ * ensure that it, too, can be overwritten by a pointer.
+ */
+ if (old_rec_type > 0) {
+ CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf);
+ if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE)
+ rec_pad(state->dst, REC_TYPE_DTXT,
+ REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf));
+ if (msg_verbose > 1)
+ msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ?
+ 30 : (int) LEN(old_rec_buf), STR(old_rec_buf));
+ }
+
+ /*
+ * If the saved record wasn't a PTR record, write the reverse pointer
+ * after the saved records. A reverse pointer value of -1 means we were
+ * confused about what we were going to save.
+ */
+ if (old_rec_type != REC_TYPE_PTR) {
+ if (next_offset < 0)
+ msg_panic("%s: bad reverse pointer %ld",
+ myname, (long) next_offset);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) next_offset);
+ if (msg_verbose > 1)
+ msg_info("%s: write PTR %ld", myname, (long) next_offset);
+ }
+
+ /*
+ * Write the forward pointer over the old record. Generally, a pointer
+ * record will be shorter than a header record, so there will be a gap in
+ * the queue file before the next record. In other words, we must always
+ * follow pointer records otherwise we get out of sync with the data.
+ */
+ if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) new_hdr_offset);
+ if (msg_verbose > 1)
+ msg_info("%s: %ld: write PTR %ld", myname, (long) old_rec_offset,
+ (long) new_hdr_offset);
+
+ /*
+ * In case of error while doing record output.
+ */
+ CLEANUP_PATCH_HEADER_RETURN(
+ CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) :
+ cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ?
+ STR(cleanup_milter_hbc_reply) : 0);
+
+ /*
+ * Note: state->append_hdr_pt_target never changes.
+ */
+}
+
+/* cleanup_ins_header - insert message header */
+
+static const char *cleanup_ins_header(void *context, ssize_t index,
+ const char *new_hdr_name,
+ const char *hdr_space,
+ const char *new_hdr_value)
+{
+ const char *myname = "cleanup_ins_header";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ VSTRING *old_rec_buf = vstring_alloc(100);
+ off_t old_rec_offset;
+ int old_rec_type;
+ off_t next_offset;
+ const char *ret;
+
+#define CLEANUP_INS_HEADER_RETURN(ret) do { \
+ vstring_free(old_rec_buf); \
+ return (ret); \
+ } while (0)
+
+ if (msg_verbose)
+ msg_info("%s: %ld \"%s\" \"%s\"",
+ myname, (long) index, new_hdr_name, new_hdr_value);
+
+ /*
+ * Look for a header at the specified position.
+ *
+ * The lookup result may be a pointer record. This allows us to make some
+ * optimization when multiple insert operations happen in the same place.
+ *
+ * Index 1 is the top-most header.
+ */
+#define NO_HEADER_NAME ((char *) 0)
+#define ALLOW_PTR_BACKUP 1
+#define SKIP_ONE_HEADER 1
+#define DONT_SKIP_HEADERS 0
+
+ if (index < 1)
+ index = 1;
+ old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME,
+ old_rec_buf, &old_rec_type,
+ ALLOW_PTR_BACKUP,
+ DONT_SKIP_HEADERS);
+ if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0));
+
+ /*
+ * If the header does not exist, simply append the header to the linked
+ * list at the "header append" pointer record.
+ */
+ if (old_rec_offset < 0)
+ CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
+ hdr_space, new_hdr_value));
+
+ /*
+ * If the header does exist, save both the new and the existing header to
+ * new storage at the end of the queue file, and link the new storage
+ * with a forward and reverse pointer (don't write a reverse pointer if
+ * we are starting with a pointer record).
+ */
+ if (old_rec_type == REC_TYPE_PTR) {
+ next_offset = -1;
+ } else {
+ if ((next_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: read file %s: %m", myname, cleanup_path);
+ CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno));
+ }
+ }
+ ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
+ old_rec_offset, old_rec_type,
+ old_rec_buf, next_offset);
+ CLEANUP_INS_HEADER_RETURN(ret);
+}
+
+/* cleanup_upd_header - modify or append message header */
+
+static const char *cleanup_upd_header(void *context, ssize_t index,
+ const char *new_hdr_name,
+ const char *hdr_space,
+ const char *new_hdr_value)
+{
+ const char *myname = "cleanup_upd_header";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ VSTRING *rec_buf;
+ off_t old_rec_offset;
+ off_t next_offset;
+ int last_type;
+ const char *ret;
+
+ if (msg_verbose)
+ msg_info("%s: %ld \"%s\" \"%s\"",
+ myname, (long) index, new_hdr_name, new_hdr_value);
+
+ /*
+ * Sanity check.
+ */
+ if (*new_hdr_name == 0)
+ msg_panic("%s: null header name", myname);
+
+ /*
+ * Find the header that is being modified.
+ *
+ * The lookup result will never be a pointer record.
+ *
+ * Index 1 is the first matching header instance.
+ *
+ * XXX When a header is updated repeatedly we create jumps to jumps. To
+ * eliminate this, rewrite the loop below so that we can start with the
+ * pointer record that points to the header that's being edited.
+ */
+#define DONT_SAVE_RECORD 0
+#define NO_PTR_BACKUP 0
+
+#define CLEANUP_UPD_HEADER_RETURN(ret) do { \
+ vstring_free(rec_buf); \
+ return (ret); \
+ } while (0)
+
+ rec_buf = vstring_alloc(100);
+ old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name,
+ rec_buf, &last_type,
+ NO_PTR_BACKUP,
+ SKIP_ONE_HEADER);
+ if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
+
+ /*
+ * If no old header is found, simply append the new header to the linked
+ * list at the "header append" pointer record.
+ */
+ if (old_rec_offset < 0)
+ CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name,
+ hdr_space, new_hdr_value));
+
+ /*
+ * If the old header is found, find the end of the old header, save the
+ * new header to new storage at the end of the queue file, and link the
+ * new storage with a forward and reverse pointer.
+ */
+ if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0));
+ ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value,
+ old_rec_offset, DONT_SAVE_RECORD,
+ (VSTRING *) 0, next_offset);
+ CLEANUP_UPD_HEADER_RETURN(ret);
+}
+
+/* cleanup_del_header - delete message header */
+
+static const char *cleanup_del_header(void *context, ssize_t index,
+ const char *hdr_name)
+{
+ const char *myname = "cleanup_del_header";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ VSTRING *rec_buf;
+ off_t header_offset;
+ off_t next_offset;
+ int last_type;
+
+ if (msg_verbose)
+ msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name);
+
+ /*
+ * Sanity check.
+ */
+ if (*hdr_name == 0)
+ msg_panic("%s: null header name", myname);
+
+ /*
+ * Find the header that is being deleted.
+ *
+ * The lookup result will never be a pointer record.
+ *
+ * Index 1 is the first matching header instance.
+ */
+#define CLEANUP_DEL_HEADER_RETURN(ret) do { \
+ vstring_free(rec_buf); \
+ return (ret); \
+ } while (0)
+
+ rec_buf = vstring_alloc(100);
+ header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf,
+ &last_type, NO_PTR_BACKUP,
+ SKIP_ONE_HEADER);
+ if (header_offset == CLEANUP_FIND_HEADER_IOERROR)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
+
+ /*
+ * Overwrite the beginning of the header record with a pointer to the
+ * information that follows the header. We can't simply overwrite the
+ * header with cleanup_out_header() and a special record type, because
+ * there may be a PTR record in the middle of a multi-line header.
+ */
+ if (header_offset > 0) {
+ if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0));
+ /* Mark the header as deleted. */
+ if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno));
+ }
+ rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) next_offset);
+ }
+ vstring_free(rec_buf);
+
+ /*
+ * In case of error while doing record output.
+ */
+ return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
+}
+
+/* cleanup_chg_from - replace sender address, ignore ESMTP arguments */
+
+static const char *cleanup_chg_from(void *context, const char *ext_from,
+ const char *esmtp_args)
+{
+ const char *myname = "cleanup_chg_from";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ off_t new_offset;
+ off_t new_sender_offset;
+ off_t after_sender_offs;
+ int addr_count;
+ TOK822 *tree;
+ TOK822 *tp;
+ VSTRING *int_sender_buf;
+ int dsn_envid = 0;
+ int dsn_ret = 0;
+
+ if (msg_verbose)
+ msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args);
+
+ /*
+ * ESMTP support is limited to RET and ENVID, i.e. things that are stored
+ * together with the sender queue file record.
+ */
+ if (esmtp_args[0]) {
+ ARGV *esmtp_argv;
+ int i;
+ const char *arg;
+
+ esmtp_argv = argv_split(esmtp_args, " ");
+ for (i = 0; i < esmtp_argv->argc; ++i) {
+ arg = esmtp_argv->argv[i];
+ if (strncasecmp(arg, "RET=", 4) == 0) {
+ if ((dsn_ret = dsn_ret_code(arg + 4)) == 0) {
+ msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
+ "SMFI_CHGFROM request", arg);
+ } else {
+ state->dsn_ret = dsn_ret;
+ }
+ } else if (strncasecmp(arg, "ENVID=", 6) == 0) {
+ if (state->milter_dsn_buf == 0)
+ state->milter_dsn_buf = vstring_alloc(20);
+ dsn_envid = (xtext_unquote(state->milter_dsn_buf, arg + 6)
+ && allprint(STR(state->milter_dsn_buf)));
+ if (!dsn_envid) {
+ msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
+ "SMFI_CHGFROM request", arg);
+ } else {
+ if (state->dsn_envid)
+ myfree(state->dsn_envid);
+ state->dsn_envid = mystrdup(STR(state->milter_dsn_buf));
+ }
+ } else {
+ msg_warn("Ignoring bad ESMTP parameter \"%s\" in "
+ "SMFI_CHGFROM request", arg);
+ }
+ }
+ argv_free(esmtp_argv);
+ }
+
+ /*
+ * The cleanup server remembers the file offset of the current sender
+ * address record (offset in sender_pt_offset) and the file offset of the
+ * record that follows the sender address (offset in sender_pt_target).
+ * Short original sender records are padded, so that they can safely be
+ * overwritten with a pointer record to the new sender address record.
+ */
+ if (state->sender_pt_offset < 0)
+ msg_panic("%s: no original sender record offset", myname);
+ if (state->sender_pt_target < 0)
+ msg_panic("%s: no post-sender record offset", myname);
+
+ /*
+ * Allocate space after the end of the queue file, and write the new {DSN
+ * envid, DSN ret, sender address, sender BCC} records, followed by a
+ * reverse pointer record that points to the record that follows the
+ * original sender record.
+ *
+ * We update the queue file in a safe manner: save the new sender after the
+ * end of the queue file, write the reverse pointer, and only then
+ * overwrite the old sender record with the forward pointer to the new
+ * sender.
+ */
+ if ((new_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+
+ /*
+ * Sender DSN attribute records precede the sender record.
+ */
+ if (dsn_envid)
+ rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ENVID, STR(state->milter_dsn_buf));
+ if (dsn_ret)
+ rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_RET, dsn_ret);
+ if (dsn_envid == 0 && dsn_ret == 0) {
+ new_sender_offset = new_offset;
+ } else if ((new_sender_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+
+ /*
+ * Transform the address from external form to internal form. This also
+ * removes the enclosing <>, if present.
+ *
+ * XXX vstring_alloc() rejects zero-length requests.
+ */
+ int_sender_buf = vstring_alloc(strlen(ext_from) + 1);
+ tree = tok822_parse(ext_from);
+ for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ if (addr_count == 0) {
+ tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL);
+ addr_count += 1;
+ } else {
+ msg_warn("%s: Milter request to add multi-sender: \"%s\"",
+ state->queue_id, ext_from);
+ break;
+ }
+ }
+ }
+ tok822_free_tree(tree);
+ after_sender_offs = cleanup_addr_sender(state, STR(int_sender_buf));
+ vstring_free(int_sender_buf);
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->sender_pt_target);
+ state->sender_pt_target = after_sender_offs;
+
+ /*
+ * Overwrite the current sender record with the pointer to the new {DSN
+ * envid, DSN ret, sender address, sender BCC} records.
+ */
+ if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) new_offset);
+
+ /*
+ * Remember the location of the new current sender record.
+ */
+ state->sender_pt_offset = new_sender_offset;
+
+ /*
+ * In case of error while doing record output.
+ */
+ return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
+}
+
+/* cleanup_add_rcpt_par - append recipient address, with ESMTP arguments */
+
+static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt,
+ const char *esmtp_args)
+{
+ const char *myname = "cleanup_add_rcpt_par";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ off_t new_rcpt_offset;
+ off_t reverse_ptr_offset;
+ int addr_count;
+ TOK822 *tree;
+ TOK822 *tp;
+ VSTRING *int_rcpt_buf;
+ VSTRING *orcpt_buf = 0;
+ ARGV *esmtp_argv;
+ int dsn_notify = 0;
+ const char *dsn_orcpt_info = 0;
+ size_t type_len;
+ int i;
+ const char *arg;
+ const char *arg_val;
+
+ if (msg_verbose)
+ msg_info("%s: \"%s\" \"%s\"", myname, ext_rcpt, esmtp_args);
+
+ /*
+ * To simplify implementation, the cleanup server writes a dummy
+ * "recipient append" pointer record after the last recipient. We cache
+ * both the location and the target of the current "recipient append"
+ * pointer record.
+ */
+ if (state->append_rcpt_pt_offset < 0)
+ msg_panic("%s: no recipient append pointer location", myname);
+ if (state->append_rcpt_pt_target < 0)
+ msg_panic("%s: no recipient append pointer target", myname);
+
+ /*
+ * Allocate space after the end of the queue file, and write the
+ * recipient record, followed by a reverse pointer record that points to
+ * the target of the old "recipient append" pointer record. This reverse
+ * pointer record becomes the new "recipient append" pointer record.
+ *
+ * We update the queue file in a safe manner: save the new recipient after
+ * the end of the queue file, write the reverse pointer, and only then
+ * overwrite the old "recipient append" pointer with the forward pointer
+ * to the new recipient.
+ */
+ if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+
+ /*
+ * Parse ESMTP parameters. XXX UTF8SMTP don't assume ORCPT is xtext.
+ */
+ if (esmtp_args[0]) {
+ esmtp_argv = argv_split(esmtp_args, " ");
+ for (i = 0; i < esmtp_argv->argc; ++i) {
+ arg = esmtp_argv->argv[i];
+ if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */
+ if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0)
+ msg_warn("%s: Bad NOTIFY parameter from Milter or "
+ "header/body_checks: \"%.100s\"",
+ state->queue_id, arg);
+ } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */
+ if (orcpt_buf == 0)
+ orcpt_buf = vstring_alloc(100);
+ if (dsn_orcpt_info
+ || (type_len = strcspn(arg_val = arg + 6, ";")) == 0
+ || (arg_val)[type_len] != ';'
+ || xtext_unquote_append(vstring_sprintf(orcpt_buf,
+ "%.*s;", (int) type_len,
+ arg_val),
+ arg_val + type_len + 1) == 0) {
+ msg_warn("%s: Bad ORCPT parameter from Milter or "
+ "header/body_checks: \"%.100s\"",
+ state->queue_id, arg);
+ } else {
+ dsn_orcpt_info = STR(orcpt_buf);
+ }
+ } else {
+ msg_warn("%s: ignoring ESMTP argument from Milter or "
+ "header/body_checks: \"%.100s\"",
+ state->queue_id, arg);
+ }
+ }
+ argv_free(esmtp_argv);
+ }
+
+ /*
+ * Transform recipient from external form to internal form. This also
+ * removes the enclosing <>, if present.
+ *
+ * XXX vstring_alloc() rejects zero-length requests.
+ */
+ int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
+ tree = tok822_parse(ext_rcpt);
+ for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ if (addr_count == 0) {
+ tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
+ addr_count += 1;
+ } else {
+ msg_warn("%s: Milter or header/body_checks request to "
+ "add multi-recipient: \"%s\"",
+ state->queue_id, ext_rcpt);
+ break;
+ }
+ }
+ }
+ tok822_free_tree(tree);
+ if (addr_count != 0)
+ cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), dsn_orcpt_info,
+ dsn_notify ? dsn_notify : DEF_DSN_NOTIFY);
+ else
+ msg_warn("%s: ignoring attempt from Milter to add null recipient",
+ state->queue_id);
+ vstring_free(int_rcpt_buf);
+ if (orcpt_buf)
+ vstring_free(orcpt_buf);
+
+ /*
+ * Don't update the queue file when we did not write a recipient record
+ * (malformed or duplicate BCC recipient).
+ */
+ if (vstream_ftell(state->dst) == new_rcpt_offset)
+ return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
+
+ /*
+ * Follow the recipient with a "reverse" pointer to the old recipient
+ * append target.
+ */
+ if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) state->append_rcpt_pt_target);
+
+ /*
+ * Pointer flipping: update the old "recipient append" pointer record
+ * value to the location of the new recipient record.
+ */
+ if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+ cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT,
+ (long) new_rcpt_offset);
+
+ /*
+ * Update the in-memory "recipient append" pointer record location with
+ * the location of the reverse pointer record that follows the new
+ * recipient. The target of the "recipient append" pointer record does
+ * not change; it's always the record that follows the dummy pointer
+ * record that was written while Postfix received the message.
+ */
+ state->append_rcpt_pt_offset = reverse_ptr_offset;
+
+ /*
+ * In case of error while doing record output.
+ */
+ return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0));
+}
+
+/* cleanup_add_rcpt - append recipient address */
+
+static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt)
+{
+ return (cleanup_add_rcpt_par(context, ext_rcpt, ""));
+}
+
+/* cleanup_del_rcpt - remove recipient and all its expansions */
+
+static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt)
+{
+ const char *myname = "cleanup_del_rcpt";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ off_t curr_offset;
+ VSTRING *buf;
+ char *attr_name;
+ char *attr_value;
+ char *dsn_orcpt = 0; /* XXX for dup filter cleanup */
+ int dsn_notify = 0; /* XXX for dup filter cleanup */
+ char *orig_rcpt = 0;
+ char *start;
+ int rec_type;
+ int junk;
+ int count = 0;
+ TOK822 *tree;
+ TOK822 *tp;
+ VSTRING *int_rcpt_buf;
+ int addr_count;
+
+ if (msg_verbose)
+ msg_info("%s: \"%s\"", myname, ext_rcpt);
+
+ /*
+ * Virtual aliasing and other address rewriting happens after the mail
+ * filter sees the envelope address. Therefore we must delete all
+ * recipient records whose Postfix (not DSN) original recipient address
+ * matches the specified address.
+ *
+ * As the number of recipients may be very large we can't do an efficient
+ * two-pass implementation (collect record offsets first, then mark
+ * records as deleted). Instead we mark records as soon as we find them.
+ * This is less efficient because we do (seek-write-read) for each marked
+ * recipient, instead of (seek-write). It's unlikely that VSTREAMs will
+ * be made smart enough to eliminate unnecessary I/O with small seeks.
+ *
+ * XXX When Postfix original recipients are turned off, we have no option
+ * but to match against the expanded and rewritten recipient address.
+ *
+ * XXX Remove the (dsn_orcpt, dsn_notify, orcpt, recip) tuple from the
+ * duplicate recipient filter. This requires that we maintain reference
+ * counts.
+ */
+ if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ return (cleanup_milter_error(state, errno));
+ }
+#define CLEANUP_DEL_RCPT_RETURN(ret) do { \
+ if (orig_rcpt != 0) \
+ myfree(orig_rcpt); \
+ if (dsn_orcpt != 0) \
+ myfree(dsn_orcpt); \
+ vstring_free(buf); \
+ vstring_free(int_rcpt_buf); \
+ return (ret); \
+ } while (0)
+
+ /*
+ * Transform recipient from external form to internal form. This also
+ * removes the enclosing <>, if present.
+ *
+ * XXX vstring_alloc() rejects zero-length requests.
+ */
+ int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1);
+ tree = tok822_parse(ext_rcpt);
+ for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) {
+ if (tp->type == TOK822_ADDR) {
+ if (addr_count == 0) {
+ tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL);
+ addr_count += 1;
+ } else {
+ msg_warn("%s: Milter request to drop multi-recipient: \"%s\"",
+ state->queue_id, ext_rcpt);
+ break;
+ }
+ }
+ }
+ tok822_free_tree(tree);
+
+ buf = vstring_alloc(100);
+ for (;;) {
+ if (CLEANUP_OUT_OK(state) == 0)
+ /* Warning and errno->error mapping are done elsewhere. */
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0));
+ if ((curr_offset = vstream_ftell(state->dst)) < 0) {
+ msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) <= 0) {
+ msg_warn("%s: read file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ if (rec_type == REC_TYPE_END)
+ break;
+ /* Skip over message content. */
+ if (rec_type == REC_TYPE_MESG) {
+ if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ continue;
+ }
+ start = STR(buf);
+ if (rec_type == REC_TYPE_PTR) {
+ if (rec_goto(state->dst, start) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ continue;
+ }
+ /* Map attribute names to pseudo record type. */
+ if (rec_type == REC_TYPE_ATTR) {
+ if (split_nameval(STR(buf), &attr_name, &attr_value) != 0
+ || *attr_value == 0)
+ continue;
+ if ((junk = rec_attr_map(attr_name)) != 0) {
+ start = attr_value;
+ rec_type = junk;
+ }
+ }
+ switch (rec_type) {
+ case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */
+ if (dsn_orcpt != 0) /* can't happen */
+ myfree(dsn_orcpt);
+ dsn_orcpt = mystrdup(start);
+ break;
+ case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */
+ if (alldig(start) && (junk = atoi(start)) > 0
+ && DSN_NOTIFY_OK(junk))
+ dsn_notify = junk;
+ else
+ dsn_notify = 0;
+ break;
+ case REC_TYPE_ORCP: /* unmodified RCPT TO address */
+ if (orig_rcpt != 0) /* can't happen */
+ myfree(orig_rcpt);
+ orig_rcpt = mystrdup(start);
+ break;
+ case REC_TYPE_RCPT: /* rewritten RCPT TO address */
+ if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) {
+ if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) {
+ msg_warn("%s: seek file %s: %m", myname, cleanup_path);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0) {
+ msg_warn("%s: write queue file: %m", state->queue_id);
+ CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno));
+ }
+ count++;
+ }
+ if (var_enable_orcpt)
+ been_here_drop(state->dups, "%s\n%d\n%s\n%s",
+ dsn_orcpt ? dsn_orcpt : "", dsn_notify,
+ orig_rcpt ? orig_rcpt : "", STR(int_rcpt_buf));
+ /* FALLTHROUGH */
+ case REC_TYPE_DRCP: /* canceled recipient */
+ case REC_TYPE_DONE: /* can't happen */
+ if (orig_rcpt != 0) {
+ myfree(orig_rcpt);
+ orig_rcpt = 0;
+ }
+ if (dsn_orcpt != 0) {
+ myfree(dsn_orcpt);
+ dsn_orcpt = 0;
+ }
+ dsn_notify = 0;
+ break;
+ }
+ }
+ if (var_enable_orcpt == 0 && count > 0)
+ been_here_drop_fixed(state->dups, STR(int_rcpt_buf));
+
+ if (msg_verbose)
+ msg_info("%s: deleted %d records for recipient \"%s\"",
+ myname, count, ext_rcpt);
+
+ CLEANUP_DEL_RCPT_RETURN(0);
+}
+
+/* cleanup_repl_body - replace message body */
+
+static const char *cleanup_repl_body(void *context, int cmd, int rec_type,
+ VSTRING *buf)
+{
+ const char *myname = "cleanup_repl_body";
+ CLEANUP_STATE *state = (CLEANUP_STATE *) context;
+ static VSTRING empty;
+
+ /*
+ * XXX Sendmail compatibility: milters don't see the first body line, so
+ * don't expect they will send one.
+ */
+ switch (cmd) {
+ case MILTER_BODY_LINE:
+ if (cleanup_body_edit_write(state, rec_type, buf) < 0)
+ return (cleanup_milter_error(state, errno));
+ break;
+ case MILTER_BODY_START:
+ VSTRING_RESET(&empty);
+ if (cleanup_body_edit_start(state) < 0
+ || cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0)
+ return (cleanup_milter_error(state, errno));
+ break;
+ case MILTER_BODY_END:
+ if (cleanup_body_edit_finish(state) < 0)
+ return (cleanup_milter_error(state, errno));
+ break;
+ default:
+ msg_panic("%s: bad command: %d", myname, cmd);
+ }
+ return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno));
+}
+
+/* cleanup_milter_eval - expand macro */
+
+static const char *cleanup_milter_eval(const char *name, void *ptr)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) ptr;
+
+ /*
+ * Note: if we use XFORWARD attributes here, then consistency requires
+ * that we forward all Sendmail macros via XFORWARD.
+ */
+
+ /*
+ * System macros.
+ */
+ if (strcmp(name, S8_MAC_DAEMON_NAME) == 0)
+ return (var_milt_daemon_name);
+ if (strcmp(name, S8_MAC_V) == 0)
+ return (var_milt_v);
+
+ /*
+ * Connect macros.
+ */
+#ifndef CLIENT_ATTR_UNKNOWN
+#define CLIENT_ATTR_UNKNOWN "unknown"
+#define SERVER_ATTR_UNKNOWN "unknown"
+#endif
+
+ if (strcmp(name, S8_MAC__) == 0) {
+ vstring_sprintf(state->temp1, "%s [%s]",
+ state->reverse_name, state->client_addr);
+ if (strcasecmp(state->client_name, state->reverse_name) != 0)
+ vstring_strcat(state->temp1, " (may be forged)");
+ return (STR(state->temp1));
+ }
+ if (strcmp(name, S8_MAC_J) == 0)
+ return (var_myhostname);
+ if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0)
+ return (state->client_addr);
+ if (strcmp(name, S8_MAC_CLIENT_NAME) == 0)
+ return (state->client_name);
+ if (strcmp(name, S8_MAC_CLIENT_PORT) == 0)
+ return (state->client_port
+ && strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ?
+ state->client_port : "0");
+ if (strcmp(name, S8_MAC_CLIENT_PTR) == 0)
+ return (state->reverse_name);
+ /* XXX S8_MAC_CLIENT_RES needs SMTPD_PEER_CODE_XXX from smtpd. */
+ if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0)
+ return (state->server_addr);
+ if (strcmp(name, S8_MAC_DAEMON_PORT) == 0)
+ return (state->server_port
+ && strcmp(state->server_port, SERVER_ATTR_UNKNOWN) ?
+ state->server_port : "0");
+
+ /*
+ * MAIL FROM macros.
+ */
+ if (strcmp(name, S8_MAC_I) == 0)
+ return (state->queue_id);
+#ifdef USE_SASL_AUTH
+ if (strcmp(name, S8_MAC_AUTH_TYPE) == 0)
+ return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD));
+ if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0)
+ return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME));
+ if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0)
+ return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER));
+#endif
+ if (strcmp(name, S8_MAC_MAIL_ADDR) == 0)
+ return (state->milter_ext_from ? STR(state->milter_ext_from) : 0);
+
+ /*
+ * RCPT TO macros.
+ */
+ if (strcmp(name, S8_MAC_RCPT_ADDR) == 0)
+ return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0);
+ return (0);
+}
+
+/* cleanup_milter_receive - receive milter instances */
+
+void cleanup_milter_receive(CLEANUP_STATE *state, int count)
+{
+ if (state->milters)
+ milter_free(state->milters);
+ state->milters = milter_receive(state->src, count);
+ if (state->milters == 0)
+ msg_fatal("cleanup_milter_receive: milter receive failed");
+ if (count <= 0)
+ return;
+ milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state);
+ milter_edit_callback(state->milters,
+ cleanup_add_header, cleanup_upd_header,
+ cleanup_ins_header, cleanup_del_header,
+ cleanup_chg_from, cleanup_add_rcpt,
+ cleanup_add_rcpt_par, cleanup_del_rcpt,
+ cleanup_repl_body, (void *) state);
+}
+
+/* cleanup_milter_apply - apply Milter response, non-zero if rejecting */
+
+static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event,
+ const char *resp)
+{
+ const char *myname = "cleanup_milter_apply";
+ const char *action;
+ const char *text;
+ const char *attr;
+ const char *ret = 0;
+
+ if (msg_verbose)
+ msg_info("%s: %s", myname, resp);
+
+ /*
+ * Don't process our own milter_header/body checks replies. See comments
+ * in cleanup_milter_hbc_extend().
+ */
+ if (cleanup_milter_hbc_reply &&
+ strcmp(resp, STR(cleanup_milter_hbc_reply)) == 0)
+ return (0);
+
+ /*
+ * Don't process Milter replies that are redundant because header/body
+ * checks already decided that we will not receive the message; or Milter
+ * replies that would have conflicting effect with the outcome of
+ * header/body checks (for example, header_checks "discard" action
+ * followed by Milter "reject" reply). Logging both actions would look
+ * silly.
+ */
+ if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) {
+ if (msg_verbose)
+ msg_info("%s: ignoring redundant or conflicting milter reply: %s",
+ state->queue_id, resp);
+ return (0);
+ }
+
+ /*
+ * Sanity check.
+ */
+ if (state->client_name == 0)
+ msg_panic("%s: missing client info initialization", myname);
+
+ /*
+ * We don't report errors that were already reported by the content
+ * editing call-back routines. See cleanup_milter_error() above.
+ */
+ if (CLEANUP_OUT_OK(state) == 0)
+ return (0);
+ switch (resp[0]) {
+ case 'H':
+ /* XXX Should log the reason here. */
+ if (state->flags & CLEANUP_FLAG_HOLD)
+ return (0);
+ state->flags |= CLEANUP_FLAG_HOLD;
+ action = "milter-hold";
+ text = "milter triggers HOLD action";
+ break;
+ case 'D':
+ state->flags |= CLEANUP_FLAG_DISCARD;
+ action = "milter-discard";
+ text = "milter triggers DISCARD action";
+ break;
+ case 'S':
+ /* XXX Can this happen after end-of-message? */
+ state->flags |= CLEANUP_STAT_CONT;
+ action = "milter-reject";
+ text = cleanup_strerror(CLEANUP_STAT_CONT);
+ break;
+
+ /*
+ * Override permanent reject with temporary reject. This happens when
+ * the cleanup server has to bounce (hard reject) but is unable to
+ * store the message (soft reject). After a temporary reject we stop
+ * inspecting queue file records, so it can't be overruled by
+ * something else.
+ *
+ * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
+ * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
+ * queue record processing, and prevents bounces from being sent.
+ */
+ case '4':
+ CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
+ ret = state->reason;
+ state->errs |= CLEANUP_STAT_DEFER;
+ action = "milter-reject";
+ text = resp + 4;
+ break;
+ case '5':
+ CLEANUP_MILTER_SET_SMTP_REPLY(state, resp);
+ ret = state->reason;
+ state->errs |= CLEANUP_STAT_CONT;
+ action = "milter-reject";
+ text = resp + 4;
+ break;
+ default:
+ msg_panic("%s: unexpected mail filter reply: %s", myname, resp);
+ }
+ vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;",
+ state->queue_id, action, event, state->client_name,
+ state->client_addr, text);
+ if (state->sender)
+ vstring_sprintf_append(state->temp1, " from=<%s>",
+ info_log_addr_form_sender(state->sender));
+ if (state->recip)
+ vstring_sprintf_append(state->temp1, " to=<%s>",
+ info_log_addr_form_recipient(state->recip));
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " proto=%s", attr);
+ if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0)
+ vstring_sprintf_append(state->temp1, " helo=<%s>", attr);
+ msg_info("%s", vstring_str(state->temp1));
+
+ return (ret);
+}
+
+/* cleanup_milter_client_init - initialize real or ersatz client info */
+
+static void cleanup_milter_client_init(CLEANUP_STATE *state)
+{
+ static INET_PROTO_INFO *proto_info;
+ const char *proto_attr;
+
+ /*
+ * Either the cleanup client specifies a name, address and protocol, or
+ * we have a local submission and pretend localhost/127.0.0.1/AF_INET.
+ */
+#define NO_CLIENT_PORT "0"
+
+ state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME);
+ state->reverse_name =
+ nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME);
+ state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR);
+ state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT);
+ proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF);
+ state->server_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_ADDR);
+ state->server_port = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_PORT);
+
+ if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0
+ || !alldig(proto_attr)) {
+ state->client_name = "localhost";
+#ifdef AF_INET6
+ if (proto_info == 0)
+ proto_info = inet_proto_info();
+ if (proto_info->sa_family_list[0] == PF_INET6) {
+ state->client_addr = "::1";
+ state->client_af = AF_INET6;
+ } else
+#endif
+ {
+ state->client_addr = "127.0.0.1";
+ state->client_af = AF_INET;
+ }
+ state->server_addr = state->client_addr;
+ } else
+ state->client_af = atoi(proto_attr);
+ if (state->reverse_name == 0)
+ state->reverse_name = state->client_name;
+ /* Compatibility with pre-2.5 queue files. */
+ if (state->client_port == 0) {
+ state->client_port = NO_CLIENT_PORT;
+ state->server_port = state->client_port;
+ }
+}
+
+/* cleanup_milter_inspect - run message through mail filter */
+
+void cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters)
+{
+ const char *myname = "cleanup_milter";
+ const char *resp;
+
+ if (msg_verbose)
+ msg_info("enter %s", myname);
+
+ /*
+ * Initialize, in case we're called via smtpd(8).
+ */
+ if (state->client_name == 0)
+ cleanup_milter_client_init(state);
+
+ /*
+ * Prologue: prepare for Milter header/body checks.
+ */
+ if (*var_milt_head_checks)
+ cleanup_milter_header_checks_reinit(state);
+
+ /*
+ * Process mail filter replies. The reply format is verified by the mail
+ * filter library.
+ */
+ if ((resp = milter_message(milters, state->handle->stream,
+ state->data_offset, state->auto_hdrs)) != 0)
+ cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
+
+ /*
+ * Epilogue: finalize Milter header/body checks.
+ */
+ if (*var_milt_head_checks)
+ cleanup_milter_hbc_finish(state);
+
+ if (msg_verbose)
+ msg_info("leave %s", myname);
+}
+
+/* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */
+
+void cleanup_milter_emul_mail(CLEANUP_STATE *state,
+ MILTERS *milters,
+ const char *addr)
+{
+ const char *resp;
+ const char *helo;
+ const char *argv[2];
+
+ /*
+ * Per-connection initialization.
+ */
+ milter_macro_callback(milters, cleanup_milter_eval, (void *) state);
+ milter_edit_callback(milters,
+ cleanup_add_header, cleanup_upd_header,
+ cleanup_ins_header, cleanup_del_header,
+ cleanup_chg_from, cleanup_add_rcpt,
+ cleanup_add_rcpt_par, cleanup_del_rcpt,
+ cleanup_repl_body, (void *) state);
+ if (state->client_name == 0)
+ cleanup_milter_client_init(state);
+
+ /*
+ * Emulate SMTP events.
+ */
+ if ((resp = milter_conn_event(milters, state->client_name, state->client_addr,
+ state->client_port, state->client_af)) != 0) {
+ cleanup_milter_apply(state, "CONNECT", resp);
+ return;
+ }
+#define PRETEND_ESMTP 1
+
+ if (CLEANUP_MILTER_OK(state)) {
+ if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0)
+ helo = state->client_name;
+ if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0) {
+ cleanup_milter_apply(state, "EHLO", resp);
+ return;
+ }
+ }
+ if (CLEANUP_MILTER_OK(state)) {
+ if (state->milter_ext_from == 0)
+ state->milter_ext_from = vstring_alloc(100);
+ /* Sendmail 8.13 does not externalize the null address. */
+ if (*addr)
+ quote_821_local(state->milter_ext_from, addr);
+ else
+ vstring_strcpy(state->milter_ext_from, addr);
+ argv[0] = STR(state->milter_ext_from);
+ argv[1] = 0;
+ if ((resp = milter_mail_event(milters, argv)) != 0) {
+ cleanup_milter_apply(state, "MAIL", resp);
+ return;
+ }
+ }
+}
+
+/* cleanup_milter_emul_rcpt - emulate rcpt event */
+
+void cleanup_milter_emul_rcpt(CLEANUP_STATE *state,
+ MILTERS *milters,
+ const char *addr)
+{
+ const char *myname = "cleanup_milter_emul_rcpt";
+ const char *resp;
+ const char *argv[2];
+
+ /*
+ * Sanity check.
+ */
+ if (state->client_name == 0)
+ msg_panic("%s: missing client info initialization", myname);
+
+ /*
+ * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason
+ * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates
+ * queue record processing, and prevents bounces from being sent.
+ */
+ if (state->milter_ext_rcpt == 0)
+ state->milter_ext_rcpt = vstring_alloc(100);
+ /* Sendmail 8.13 does not externalize the null address. */
+ if (*addr)
+ quote_821_local(state->milter_ext_rcpt, addr);
+ else
+ vstring_strcpy(state->milter_ext_rcpt, addr);
+ argv[0] = STR(state->milter_ext_rcpt);
+ argv[1] = 0;
+ if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0
+ && cleanup_milter_apply(state, "RCPT", resp) != 0) {
+ msg_warn("%s: milter configuration error: can't reject recipient "
+ "in non-smtpd(8) submission", state->queue_id);
+ msg_warn("%s: message not accepted, try again later", state->queue_id);
+ CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error");
+ state->errs |= CLEANUP_STAT_DEFER;
+ }
+}
+
+/* cleanup_milter_emul_data - emulate data event */
+
+void cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters)
+{
+ const char *myname = "cleanup_milter_emul_data";
+ const char *resp;
+
+ /*
+ * Sanity check.
+ */
+ if (state->client_name == 0)
+ msg_panic("%s: missing client info initialization", myname);
+
+ if ((resp = milter_data_event(milters)) != 0)
+ cleanup_milter_apply(state, "DATA", resp);
+}
+
+#ifdef TEST
+
+ /*
+ * Queue file editing driver for regression tests. In this case it is OK to
+ * report fatal errors after I/O errors.
+ */
+#include <stdio.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <mail_addr.h>
+#include <mail_version.h>
+
+#undef msg_verbose
+
+char *cleanup_path;
+VSTRING *cleanup_trace_path;
+VSTRING *cleanup_strip_chars;
+int cleanup_comm_canon_flags;
+MAPS *cleanup_comm_canon_maps;
+int cleanup_ext_prop_mask;
+ARGV *cleanup_masq_domains;
+int cleanup_masq_flags;
+MAPS *cleanup_rcpt_bcc_maps;
+int cleanup_rcpt_canon_flags;
+MAPS *cleanup_rcpt_canon_maps;
+MAPS *cleanup_send_bcc_maps;
+int cleanup_send_canon_flags;
+MAPS *cleanup_send_canon_maps;
+int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
+char *var_empty_addr = DEF_EMPTY_ADDR;
+MAPS *cleanup_virt_alias_maps;
+char *var_milt_daemon_name = "host.example.com";
+char *var_milt_v = DEF_MILT_V;
+MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters));
+char *var_milt_head_checks = "";
+
+/* Dummies to satisfy unused external references. */
+
+int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains)
+{
+ msg_panic("cleanup_masquerade_internal dummy");
+}
+
+int cleanup_rewrite_internal(const char *context, VSTRING *result,
+ const char *addr)
+{
+ vstring_strcpy(result, addr);
+ return (0);
+}
+
+int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
+ MAPS *maps, int propagate)
+{
+ msg_panic("cleanup_map11_internal dummy");
+}
+
+ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr,
+ MAPS *maps, int propagate)
+{
+ msg_panic("cleanup_map1n_internal dummy");
+}
+
+void cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf,
+ ssize_t len)
+{
+ msg_panic("cleanup_envelope dummy");
+}
+
+static void usage(void)
+{
+ msg_warn("usage:");
+ msg_warn(" verbose on|off");
+ msg_warn(" open pathname");
+ msg_warn(" close");
+ msg_warn(" add_header index name [value]");
+ msg_warn(" ins_header index name [value]");
+ msg_warn(" upd_header index name [value]");
+ msg_warn(" del_header index name");
+ msg_warn(" chg_from addr parameters");
+ msg_warn(" add_rcpt addr");
+ msg_warn(" add_rcpt_par addr parameters");
+ msg_warn(" del_rcpt addr");
+ msg_warn(" replbody pathname");
+ msg_warn(" header_checks type:name");
+}
+
+/* flatten_args - unparse partial command line */
+
+static void flatten_args(VSTRING *buf, char **argv)
+{
+ char **cpp;
+
+ VSTRING_RESET(buf);
+ for (cpp = argv; *cpp; cpp++) {
+ vstring_strcat(buf, *cpp);
+ if (cpp[1])
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+}
+
+/* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */
+
+static void open_queue_file(CLEANUP_STATE *state, const char *path)
+{
+ VSTRING *buf = vstring_alloc(100);
+ off_t curr_offset;
+ int rec_type;
+ long msg_seg_len;
+ long data_offset;
+ long rcpt_count;
+ long qmgr_opts;
+
+ if (state->dst != 0) {
+ msg_warn("closing %s", cleanup_path);
+ vstream_fclose(state->dst);
+ state->dst = 0;
+ myfree(cleanup_path);
+ cleanup_path = 0;
+ }
+ if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) {
+ msg_warn("open %s: %m", path);
+ } else {
+ cleanup_path = mystrdup(path);
+ for (;;) {
+ if ((curr_offset = vstream_ftell(state->dst)) < 0)
+ msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
+ if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0)
+ msg_fatal("file %s: missing SIZE or PTR record", cleanup_path);
+ if (rec_type == REC_TYPE_SIZE) {
+ if (sscanf(STR(buf), "%ld %ld %ld %ld",
+ &msg_seg_len, &data_offset,
+ &rcpt_count, &qmgr_opts) != 4)
+ msg_fatal("file %s: bad SIZE record: %s",
+ cleanup_path, STR(buf));
+ state->data_offset = data_offset;
+ state->xtra_offset = data_offset + msg_seg_len;
+ } else if (rec_type == REC_TYPE_FROM) {
+ state->sender_pt_offset = curr_offset;
+ if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE
+ && rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR)
+ msg_fatal("file %s: missing PTR record after short sender",
+ cleanup_path);
+ if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0)
+ msg_fatal("file %s: missing END record", cleanup_path);
+ } else if (rec_type == REC_TYPE_PTR) {
+ if (state->data_offset < 0)
+ msg_fatal("file %s: missing SIZE record", cleanup_path);
+ if (curr_offset < state->data_offset
+ || curr_offset > state->xtra_offset) {
+ if (state->append_rcpt_pt_offset < 0) {
+ state->append_rcpt_pt_offset = curr_offset;
+ if (atol(STR(buf)) != 0)
+ msg_fatal("file %s: bad dummy recipient PTR record: %s",
+ cleanup_path, STR(buf));
+ if ((state->append_rcpt_pt_target =
+ vstream_ftell(state->dst)) < 0)
+ msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
+ } else if (curr_offset > state->xtra_offset
+ && state->append_meta_pt_offset < 0) {
+ state->append_meta_pt_offset = curr_offset;
+ if (atol(STR(buf)) != 0)
+ msg_fatal("file %s: bad dummy meta PTR record: %s",
+ cleanup_path, STR(buf));
+ if ((state->append_meta_pt_target =
+ vstream_ftell(state->dst)) < 0)
+ msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
+ }
+ } else {
+ if (state->append_hdr_pt_offset < 0) {
+ state->append_hdr_pt_offset = curr_offset;
+ if (atol(STR(buf)) != 0)
+ msg_fatal("file %s: bad dummy header PTR record: %s",
+ cleanup_path, STR(buf));
+ if ((state->append_hdr_pt_target =
+ vstream_ftell(state->dst)) < 0)
+ msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
+ }
+ }
+ }
+ if (state->append_rcpt_pt_offset > 0
+ && state->append_hdr_pt_offset > 0
+ && (rec_type == REC_TYPE_END
+ || state->append_meta_pt_offset > 0))
+ break;
+ }
+ if (msg_verbose) {
+ msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld",
+ (long) state->append_rcpt_pt_offset,
+ (long) state->append_rcpt_pt_target);
+ msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld",
+ (long) state->append_hdr_pt_offset,
+ (long) state->append_hdr_pt_target);
+ }
+ }
+ vstring_free(buf);
+}
+
+static void close_queue_file(CLEANUP_STATE *state)
+{
+ (void) vstream_fclose(state->dst);
+ state->dst = 0;
+ myfree(cleanup_path);
+ cleanup_path = 0;
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *inbuf = vstring_alloc(100);
+ VSTRING *arg_buf = vstring_alloc(100);
+ char *bufp;
+ int istty = isatty(vstream_fileno(VSTREAM_IN));
+ CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0);
+ const char *parens = "{}";
+
+ state->queue_id = mystrdup("NOQUEUE");
+ state->sender = mystrdup("sender");
+ state->recip = mystrdup("recipient");
+ state->client_name = "client_name";
+ state->client_addr = "client_addr";
+ state->flags |= CLEANUP_FLAG_FILTER_ALL;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ var_line_limit = DEF_LINE_LIMIT;
+ var_header_limit = DEF_HEADER_LIMIT;
+ var_enable_orcpt = DEF_ENABLE_ORCPT;
+ var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM;
+
+ for (;;) {
+ ARGV *argv;
+ ssize_t index;
+ const char *resp = 0;
+
+ if (istty) {
+ vstream_printf("- ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
+ break;
+
+ bufp = vstring_str(inbuf);
+ if (!istty) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#' || *bufp == 0 || allspace(bufp))
+ continue;
+ argv = argv_splitq(bufp, " ", parens);
+ if (argv->argc == 0) {
+ msg_warn("missing command");
+ } else if (strcmp(argv->argv[0], "?") == 0) {
+ usage();
+ } else if (strcmp(argv->argv[0], "verbose") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad verbose argument count: %ld", (long) argv->argc);
+ } else if (strcmp(argv->argv[1], "on") == 0) {
+ msg_verbose = 2;
+ } else if (strcmp(argv->argv[1], "off") == 0) {
+ msg_verbose = 0;
+ } else {
+ msg_warn("bad verbose argument");
+ }
+ } else if (strcmp(argv->argv[0], "open") == 0) {
+ if (state->dst != 0) {
+ msg_info("closing %s", VSTREAM_PATH(state->dst));
+ close_queue_file(state);
+ }
+ if (argv->argc != 2) {
+ msg_warn("bad open argument count: %ld", (long) argv->argc);
+ } else {
+ open_queue_file(state, argv->argv[1]);
+ }
+ } else if (state->dst == 0) {
+ msg_warn("no open queue file");
+ } else if (strcmp(argv->argv[0], "close") == 0) {
+ if (*var_milt_head_checks) {
+ cleanup_milter_hbc_finish(state);
+ myfree(var_milt_head_checks);
+ var_milt_head_checks = "";
+ cleanup_milter_header_checks_deinit();
+ }
+ close_queue_file(state);
+ } else if (cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply)) {
+ /* Postfix libmilter would skip further requests. */
+ msg_info("ignoring: %s %s %s", argv->argv[0],
+ argv->argc > 1 ? argv->argv[1] : "",
+ argv->argc > 2 ? argv->argv[2] : "");
+ } else if (strcmp(argv->argv[0], "add_header") == 0) {
+ if (argv->argc < 2) {
+ msg_warn("bad add_header argument count: %ld",
+ (long) argv->argc);
+ } else {
+ flatten_args(arg_buf, argv->argv + 2);
+ resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf));
+ }
+ } else if (strcmp(argv->argv[0], "ins_header") == 0) {
+ if (argv->argc < 3) {
+ msg_warn("bad ins_header argument count: %ld",
+ (long) argv->argc);
+ } else if ((index = atoi(argv->argv[1])) < 1) {
+ msg_warn("bad ins_header index value");
+ } else {
+ flatten_args(arg_buf, argv->argv + 3);
+ resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf));
+ }
+ } else if (strcmp(argv->argv[0], "upd_header") == 0) {
+ if (argv->argc < 3) {
+ msg_warn("bad upd_header argument count: %ld",
+ (long) argv->argc);
+ } else if ((index = atoi(argv->argv[1])) < 1) {
+ msg_warn("bad upd_header index value");
+ } else {
+ flatten_args(arg_buf, argv->argv + 3);
+ resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf));
+ }
+ } else if (strcmp(argv->argv[0], "del_header") == 0) {
+ if (argv->argc != 3) {
+ msg_warn("bad del_header argument count: %ld",
+ (long) argv->argc);
+ } else if ((index = atoi(argv->argv[1])) < 1) {
+ msg_warn("bad del_header index value");
+ } else {
+ cleanup_del_header(state, index, argv->argv[2]);
+ }
+ } else if (strcmp(argv->argv[0], "chg_from") == 0) {
+ if (argv->argc != 3) {
+ msg_warn("bad chg_from argument count: %ld", (long) argv->argc);
+ } else {
+ char *arg = argv->argv[2];
+ const char *err;
+
+ if (*arg == parens[0]
+ && (err = extpar(&arg, parens, EXTPAR_FLAG_NONE)) != 0) {
+ msg_warn("%s in \"%s\"", err, arg);
+ } else {
+ cleanup_chg_from(state, argv->argv[1], arg);
+ }
+ }
+ } else if (strcmp(argv->argv[0], "add_rcpt") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad add_rcpt argument count: %ld", (long) argv->argc);
+ } else {
+ cleanup_add_rcpt(state, argv->argv[1]);
+ }
+ } else if (strcmp(argv->argv[0], "add_rcpt_par") == 0) {
+ if (argv->argc != 3) {
+ msg_warn("bad add_rcpt_par argument count: %ld",
+ (long) argv->argc);
+ } else {
+ cleanup_add_rcpt_par(state, argv->argv[1], argv->argv[2]);
+ }
+ } else if (strcmp(argv->argv[0], "del_rcpt") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad del_rcpt argument count: %ld", (long) argv->argc);
+ } else {
+ cleanup_del_rcpt(state, argv->argv[1]);
+ }
+ } else if (strcmp(argv->argv[0], "replbody") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad replbody argument count: %ld", (long) argv->argc);
+ } else {
+ VSTREAM *fp;
+ VSTRING *buf;
+
+ if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) {
+ msg_warn("open %s file: %m", argv->argv[1]);
+ } else {
+ buf = vstring_alloc(100);
+ cleanup_repl_body(state, MILTER_BODY_START, buf);
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
+ cleanup_repl_body(state, MILTER_BODY_LINE, buf);
+ cleanup_repl_body(state, MILTER_BODY_END, buf);
+ vstring_free(buf);
+ vstream_fclose(fp);
+ }
+ }
+ } else if (strcmp(argv->argv[0], "header_checks") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad header_checks argument count: %ld",
+ (long) argv->argc);
+ } else if (*var_milt_head_checks) {
+ msg_warn("can't change header checks");
+ } else {
+ var_milt_head_checks = mystrdup(argv->argv[1]);
+ cleanup_milter_header_checks_init();
+ }
+ } else if (strcmp(argv->argv[0], "sender_bcc_maps") == 0) {
+ if (argv->argc != 2) {
+ msg_warn("bad sender_bcc_maps argument count: %ld",
+ (long) argv->argc);
+ } else {
+ if (cleanup_send_bcc_maps)
+ maps_free(cleanup_send_bcc_maps);
+ cleanup_send_bcc_maps =
+ maps_create("sender_bcc_maps", argv->argv[1],
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ state->flags |= CLEANUP_FLAG_BCC_OK;
+ var_rcpt_delim = "";
+ }
+ } else {
+ msg_warn("bad command: %s", argv->argv[0]);
+ }
+ argv_free(argv);
+ if (resp)
+ cleanup_milter_apply(state, "END-OF-MESSAGE", resp);
+ }
+ vstring_free(inbuf);
+ vstring_free(arg_buf);
+ if (state->append_meta_pt_offset >= 0) {
+ if (state->flags)
+ msg_info("flags = %s", cleanup_strflags(state->flags));
+ if (state->errs)
+ msg_info("errs = %s", cleanup_strerror(state->errs));
+ }
+ cleanup_state_free(state);
+ if (*var_milt_head_checks)
+ myfree(var_milt_head_checks);
+
+ return (0);
+}
+
+#endif
diff --git a/src/cleanup/cleanup_milter.in1 b/src/cleanup/cleanup_milter.in1
new file mode 100644
index 0000000..85cc3ea
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in1
@@ -0,0 +1,42 @@
+#verbose on
+open test-queue-file.tmp
+
+# Add and remove some recipient records. We verify that all results
+# from virtual alias expansion are deleted. We don't yet attempt to
+# remove non-existent recipients.
+
+add_rcpt xxxx
+add_rcpt yyyy
+del_rcpt alias@hades.porcupine.org
+del_rcpt yyyy
+
+# Insert a short header X2 at the position of a short multi-line
+# header X, so that the first part of the multi-line header X needs
+# to be copied to the heap, right after the inserted header. Then
+# update the inserted header X2, so that a smaller portion of the
+# saved multi-line header X needs to be copied again. Thus we end
+# up with a multi-line header X that is broken up into three pieces.
+# Finally, delete the inserted header X2. All this tests if an insert
+# operation properly saves a portion of a multi-line header, to make
+# space for the forward pointer to the inserted content.
+
+ins_header 2 X2 v1
+upd_header 1 X2 v2
+del_header 1 X2
+
+# Insert a header at the position of a single-line short header Y,
+# so that both header Y, and the single-line Message-ID header that
+# follows Y, need to be copied to the heap. This tests if an insert
+# operation properly saves records to make space for the forward
+# pointer record to the inserted content.
+
+ins_header 3 X2 test header value 3
+
+# Update the multiply broken, multi-line, header X. This tests if
+# we correcly link the new header to the header that comes after the
+# modified header.
+
+upd_header 1 X X-replaced-header replacement header text
+#upd_header 1 X X-replaced-header replacement header text
+#upd_header 1 X X-replaced-header replacement header text
+close
diff --git a/src/cleanup/cleanup_milter.in10a b/src/cleanup/cleanup_milter.in10a
new file mode 100644
index 0000000..997b8a7
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in10a
@@ -0,0 +1,11 @@
+#
+# Replace a short body by a longer one. The result is two body
+# regions: the original region with the head of the new text, and
+# one region at the end of the queue file with the remainder of the
+# new text.
+#
+open test-queue-file10.tmp
+
+replbody loremipsum
+
+close
diff --git a/src/cleanup/cleanup_milter.in10b b/src/cleanup/cleanup_milter.in10b
new file mode 100644
index 0000000..ba3b07f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in10b
@@ -0,0 +1,12 @@
+#
+# Replace a short body by a longer one, and then replace the longer
+# body by itself. The result should be identical to what we had after
+# one replacement.
+#
+#verbose on
+open test-queue-file10.tmp
+
+replbody loremipsum
+replbody loremipsum
+
+close
diff --git a/src/cleanup/cleanup_milter.in10c b/src/cleanup/cleanup_milter.in10c
new file mode 100644
index 0000000..79af0ce
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in10c
@@ -0,0 +1,14 @@
+#
+# Replace a short body by a longer one, and then clobber that with
+# an even longer body. The result is three body regions: the original
+# region, the region that contained the tail of the first replacement,
+# and a region that contains the tail of the second replacement.
+#
+
+#verbose on
+open test-queue-file10.tmp
+
+replbody loremipsum
+replbody loremipsum2
+
+close
diff --git a/src/cleanup/cleanup_milter.in10d b/src/cleanup/cleanup_milter.in10d
new file mode 100644
index 0000000..b7c515a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in10d
@@ -0,0 +1,14 @@
+#
+# As test 10c, but then replace the longer body by the shorter one. The
+# result is three regions: the original region, the region with the
+# tail of the shorter replacement, and an unused region that contained
+# the tail of the larger region.
+#
+#verbose on
+open test-queue-file10.tmp
+
+replbody loremipsum
+replbody loremipsum2
+replbody loremipsum
+
+close
diff --git a/src/cleanup/cleanup_milter.in10e b/src/cleanup/cleanup_milter.in10e
new file mode 100644
index 0000000..763f79b
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in10e
@@ -0,0 +1,13 @@
+#
+# Replace a short body by increasingly longer ones and do header
+# updates in between.
+#
+#verbose on
+open test-queue-file10.tmp
+
+add_header foo1 foobar
+replbody loremipsum
+add_header foo2 foobar
+replbody loremipsum2
+
+close
diff --git a/src/cleanup/cleanup_milter.in11 b/src/cleanup/cleanup_milter.in11
new file mode 100644
index 0000000..8e69f71
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in11
@@ -0,0 +1,10 @@
+#
+# Replace a non-existent body by a non-empty one.
+#
+#verbose on
+open test-queue-file11.tmp
+
+replbody loremipsum
+replbody loremipsum
+
+close
diff --git a/src/cleanup/cleanup_milter.in12 b/src/cleanup/cleanup_milter.in12
new file mode 100644
index 0000000..bf44e60
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in12
@@ -0,0 +1,22 @@
+#verbose on
+open test-queue-file12.tmp
+
+# Add a recipient to a message that was received with "sendmail -t"
+# so that all the recipients are in the extracted queue file segment.
+
+add_rcpt me@porcupine.org
+
+# Delete the recipient added above.
+
+del_rcpt me@porcupine.org
+
+# Add a new recipient, using a different address than above, so that
+# the duplicate filter won't suppress it.
+
+add_rcpt em@porcupine.org
+
+# Delete the recipient.
+
+del_rcpt em@porcupine.org
+
+close
diff --git a/src/cleanup/cleanup_milter.in13a b/src/cleanup/cleanup_milter.in13a
new file mode 100644
index 0000000..ab0f531
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13a
@@ -0,0 +1,22 @@
+#verbose on
+open test-queue-file13a.tmp
+
+# Add a recipient to a message that was received with "sendmail -t"
+# so that all the recipients are in the extracted queue file segment.
+
+add_rcpt_par me@porcupine.org esmtpstuff
+
+# Delete the recipient added above.
+
+del_rcpt me@porcupine.org
+
+# Add a new recipient, using a different address than above, so that
+# the duplicate filter won't suppress it.
+
+add_rcpt_par em@porcupine.org esmtpstuff
+
+# Delete the recipient.
+
+del_rcpt em@porcupine.org
+
+close
diff --git a/src/cleanup/cleanup_milter.in13b b/src/cleanup/cleanup_milter.in13b
new file mode 100644
index 0000000..04ef9e2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13b
@@ -0,0 +1,8 @@
+#verbose on
+open test-queue-file13b.tmp
+
+# Change the sender.
+
+chg_from m@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13c b/src/cleanup/cleanup_milter.in13c
new file mode 100644
index 0000000..8bfa292
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13c
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file13c.tmp
+
+# Change the sender.
+
+chg_from m@porcupine.org esmtpstuff
+chg_from n@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13d b/src/cleanup/cleanup_milter.in13d
new file mode 100644
index 0000000..da673fe
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13d
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file13d.tmp
+
+# Change the null sender, to test correct padding of short sender records.
+
+chg_from m@porcupine.org esmtpstuff
+chg_from n@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13e b/src/cleanup/cleanup_milter.in13e
new file mode 100644
index 0000000..a657c3c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13e
@@ -0,0 +1,10 @@
+#verbose on
+open test-queue-file13e.tmp
+
+# Change the sender.
+
+sender_bcc_maps static:a@porcupine.org
+chg_from m@porcupine.org esmtpstuff
+#chg_from n@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13f b/src/cleanup/cleanup_milter.in13f
new file mode 100644
index 0000000..aeb7f5a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13f
@@ -0,0 +1,10 @@
+#verbose on
+open test-queue-file13f.tmp
+
+# Change the sender.
+
+sender_bcc_maps static:a@porcupine.org
+chg_from m@porcupine.org esmtpstuff
+chg_from n@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13g b/src/cleanup/cleanup_milter.in13g
new file mode 100644
index 0000000..c88e3c9
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13g
@@ -0,0 +1,11 @@
+#verbose on
+open test-queue-file13g.tmp
+
+# Change the sender.
+
+sender_bcc_maps static:a@porcupine.org
+chg_from m@porcupine.org esmtpstuff
+chg_from n@porcupine.org esmtpstuff
+chg_from o@porcupine.org esmtpstuff
+
+close
diff --git a/src/cleanup/cleanup_milter.in13h b/src/cleanup/cleanup_milter.in13h
new file mode 100644
index 0000000..b5af323
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13h
@@ -0,0 +1,8 @@
+#verbose on
+open test-queue-file13h.tmp
+
+# Change the sender.
+
+chg_from m@porcupine.org { ret=hdrs envid=env-for-m }
+
+close
diff --git a/src/cleanup/cleanup_milter.in13i b/src/cleanup/cleanup_milter.in13i
new file mode 100644
index 0000000..85dfeb0
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in13i
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file13i.tmp
+
+# Change the sender.
+
+chg_from m@porcupine.org { ret=hdrs envid=env-for-m }
+chg_from n@porcupine.org { ret=full envid=env-for-n }
+
+close
diff --git a/src/cleanup/cleanup_milter.in14a b/src/cleanup/cleanup_milter.in14a
new file mode 100644
index 0000000..6fc21f5
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14a
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14a.tmp
+
+header_checks regexp:cleanup_milter.reg14a
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14b b/src/cleanup/cleanup_milter.in14b
new file mode 100644
index 0000000..539112c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14b
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14b.tmp
+
+header_checks regexp:cleanup_milter.reg14b
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14c b/src/cleanup/cleanup_milter.in14c
new file mode 100644
index 0000000..0a247b2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14c
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14c.tmp
+
+header_checks regexp:cleanup_milter.reg14c
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14d b/src/cleanup/cleanup_milter.in14d
new file mode 100644
index 0000000..13aa2ef
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14d
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14d.tmp
+
+header_checks regexp:cleanup_milter.reg14d
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14e b/src/cleanup/cleanup_milter.in14e
new file mode 100644
index 0000000..f54ccd0
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14e
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14e.tmp
+
+header_checks regexp:cleanup_milter.reg14e
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14f b/src/cleanup/cleanup_milter.in14f
new file mode 100644
index 0000000..68124a7
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14f
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14f.tmp
+
+header_checks regexp:cleanup_milter.reg14f
+ins_header 2 X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in14g b/src/cleanup/cleanup_milter.in14g
new file mode 100644
index 0000000..ebd866f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in14g
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file14g.tmp
+
+header_checks regexp:cleanup_milter.reg14g
+upd_header 1 X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15a b/src/cleanup/cleanup_milter.in15a
new file mode 100644
index 0000000..8c2be9e
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15a
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15a.tmp
+
+header_checks regexp:cleanup_milter.reg15a
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15b b/src/cleanup/cleanup_milter.in15b
new file mode 100644
index 0000000..fb209ad
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15b
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15b.tmp
+
+header_checks regexp:cleanup_milter.reg15b
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15c b/src/cleanup/cleanup_milter.in15c
new file mode 100644
index 0000000..3b3ef36
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15c
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15c.tmp
+
+header_checks regexp:cleanup_milter.reg15c
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15d b/src/cleanup/cleanup_milter.in15d
new file mode 100644
index 0000000..a00b143
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15d
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15d.tmp
+
+header_checks regexp:cleanup_milter.reg15d
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15e b/src/cleanup/cleanup_milter.in15e
new file mode 100644
index 0000000..1c26f59
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15e
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15e.tmp
+
+header_checks regexp:cleanup_milter.reg15e
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15f b/src/cleanup/cleanup_milter.in15f
new file mode 100644
index 0000000..8dc6a26
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15f
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15f.tmp
+
+header_checks regexp:cleanup_milter.reg15f
+ins_header 2 X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15g b/src/cleanup/cleanup_milter.in15g
new file mode 100644
index 0000000..0e90d9f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15g
@@ -0,0 +1,7 @@
+#verbose on
+open test-queue-file15g.tmp
+
+header_checks regexp:cleanup_milter.reg15g
+ins_header 2 X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in15h b/src/cleanup/cleanup_milter.in15h
new file mode 100644
index 0000000..3538f79
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15h
@@ -0,0 +1,11 @@
+#verbose on
+open test-queue-file15h.tmp
+
+# Test the CLEANUP_FILTER_FLAG_ALL feature. The first header with
+# YES clears the flag, and the second add_header is ignored.
+
+header_checks regexp:cleanup_milter.reg15h
+add_header X-SPAM-FLAG YES
+add_header X-SPAM-FLAG NO
+
+close
diff --git a/src/cleanup/cleanup_milter.in15i b/src/cleanup/cleanup_milter.in15i
new file mode 100644
index 0000000..454036a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in15i
@@ -0,0 +1,11 @@
+#verbose on
+open test-queue-file15i.tmp
+
+# Test the CLEANUP_STAT_CONT flag. The first header triggers FILTER,
+# but the second header triggers REJECT, so the filter is not saved.
+
+header_checks regexp:cleanup_milter.reg15i
+add_header X-SPAM-FLAG NO
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in16a b/src/cleanup/cleanup_milter.in16a
new file mode 100644
index 0000000..3b7da44
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in16a
@@ -0,0 +1,10 @@
+#verbose on
+open test-queue-file16a.tmp
+
+# Test the BCC action in milter_header_checks.
+
+header_checks regexp:cleanup_milter.reg16a
+add_header X-SPAM-FLAG NO
+add_header X-SPAM-FLAG YES
+
+close
diff --git a/src/cleanup/cleanup_milter.in16b b/src/cleanup/cleanup_milter.in16b
new file mode 100644
index 0000000..57f6e24
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in16b
@@ -0,0 +1,12 @@
+#verbose on
+open test-queue-file16b.tmp
+
+# Test the add_rcpt_par action
+
+add_rcpt_par foo@example.com notify=never
+add_rcpt_par foo@example.com notify=never
+add_rcpt bar@example.com
+add_rcpt_par bar@example.com orcpt=rfc822;orig-bar@example.com
+add_rcpt_par bar@example.com notify=delay
+
+close
diff --git a/src/cleanup/cleanup_milter.in2 b/src/cleanup/cleanup_milter.in2
new file mode 100644
index 0000000..c459a9f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in2
@@ -0,0 +1,28 @@
+#verbose on
+open test-queue-file2.tmp
+
+# Update a short Subject: header that immediately precedes the "append
+# header" pointer record. The new Subject: header value is stored
+# at the end of the heap, followed by the saved "append header"
+# pointer record value.
+#
+# - Postfix must not consider the "append header" pointer record as
+# if it were part of the short Subject: header. Instead, the record
+# must be saved to the heap, right after the new Subject: header
+# value.
+#
+# - Postfix must update its idea of the current "append header"
+# pointer record location.
+#
+# - While saving the "append header" pointer record value, Postfix
+# must replace a "0" append header" pointer record value by the
+# actual location of the message body content.
+
+upd_header 1 Subject hey!
+upd_header 1 Subject hey!
+upd_header 1 Subject hey!
+add_header foo foobar
+upd_header 1 foo foobar
+upd_header 1 foo foobar
+upd_header 1 foo foobar
+close
diff --git a/src/cleanup/cleanup_milter.in3 b/src/cleanup/cleanup_milter.in3
new file mode 100644
index 0000000..8a9b412
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in3
@@ -0,0 +1,60 @@
+#verbose on
+open test-queue-file3.tmp
+
+# Another torture test for mail with a short last message header.
+# This complements test #2 with the same message where we update the
+# short Subject header, but none of the other headers. Like test #1,
+# this also tests possible interactions with envelope recipient
+# updates, which share the same heap with message header updates.
+
+# Add a recipient and update all headers in reverse order.
+
+add_rcpt me@porcupine.org
+upd_header 1 Subject hey!
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 To you@porcupine.org
+upd_header 1 From me@porcupine.org
+
+# Delete the recipient added above, and update headers in reverse
+# order, twice. This tests repeated updates of short headers, but
+# doesn't test much for the longer ones.
+
+del_rcpt me@porcupine.org
+upd_header 1 Subject hey!
+upd_header 1 Subject hey!
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 To you@porcupine.org
+upd_header 1 To you@porcupine.org
+upd_header 1 From me@porcupine.org
+upd_header 1 From me@porcupine.org
+
+# Add a new recipient, using a different address than above, so that
+# the duplicate filter won't suppress it. Update the headers in the
+# normal order, in case it makes a difference.
+
+add_rcpt em@porcupine.org
+upd_header 1 From me@porcupine.org
+upd_header 1 To you@porcupine.org
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Subject hey!
+
+# Delete the recipient and update the headers again.
+
+del_rcpt em@porcupine.org
+upd_header 1 From me@porcupine.org
+upd_header 1 From me@porcupine.org
+upd_header 1 To you@porcupine.org
+upd_header 1 To you@porcupine.org
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org>
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+upd_header 1 Subject hey!
+upd_header 1 Subject hey!
+
+close
diff --git a/src/cleanup/cleanup_milter.in4a b/src/cleanup/cleanup_milter.in4a
new file mode 100644
index 0000000..ce07907
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in4a
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file4.tmp
+add_rcpt 01
+add_rcpt 02
+add_rcpt 03
+del_rcpt 03
+del_rcpt 02
+del_rcpt 01
+close
diff --git a/src/cleanup/cleanup_milter.in4b b/src/cleanup/cleanup_milter.in4b
new file mode 100644
index 0000000..3e6202d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in4b
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file4.tmp
+add_rcpt 01
+add_rcpt 02
+add_rcpt 03
+del_rcpt 01
+del_rcpt 02
+del_rcpt 03
+close
diff --git a/src/cleanup/cleanup_milter.in4c b/src/cleanup/cleanup_milter.in4c
new file mode 100644
index 0000000..25dc6d6
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in4c
@@ -0,0 +1,12 @@
+#verbose on
+open test-queue-file4.tmp
+add_rcpt 01
+del_rcpt 01
+del_rcpt 03
+add_rcpt 02
+del_rcpt 02
+del_rcpt 01
+add_rcpt 03
+del_rcpt 03
+del_rcpt 02
+close
diff --git a/src/cleanup/cleanup_milter.in5 b/src/cleanup/cleanup_milter.in5
new file mode 100644
index 0000000..393e300
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in5
@@ -0,0 +1,19 @@
+open test-queue-file5.tmp
+
+# Test with a series of multiple short headers.
+
+# Update short multi-line X header in the middle of other headers,
+# so that the next header gets copied right after the new X header.
+# Then update the X header another time so that it separates from
+# the header that follows it.
+
+upd_header 1 X whatevershebringswesing
+upd_header 1 X whatevershebringswesing
+
+# Update a short Subject header that precedes the updated X header,
+# and see if pointers are updated properly.
+
+upd_header 1 Subject hya
+#upd_header 1 Subject hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in6a b/src/cleanup/cleanup_milter.in6a
new file mode 100644
index 0000000..aec3d3a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in6a
@@ -0,0 +1,5 @@
+open test-queue-file6.tmp
+
+ins_header 1 X-Virus-Scanned hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in6b b/src/cleanup/cleanup_milter.in6b
new file mode 100644
index 0000000..832a54a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in6b
@@ -0,0 +1,6 @@
+open test-queue-file6.tmp
+
+ins_header 1 X-Virus-Scanned hya
+ins_header 2 Domainkey-Signature hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in6c b/src/cleanup/cleanup_milter.in6c
new file mode 100644
index 0000000..3ef53ff
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in6c
@@ -0,0 +1,7 @@
+open test-queue-file6.tmp
+
+ins_header 1 X-Virus-Scanned hya
+ins_header 2 Domainkey-Signature hya
+ins_header 2 DKIM-Signature hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in7 b/src/cleanup/cleanup_milter.in7
new file mode 100644
index 0000000..13ff78c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in7
@@ -0,0 +1,7 @@
+open test-queue-file7.tmp
+
+ins_header 2 X-Virus-Scanned hya
+ins_header 2 Domainkey-Signature hya
+ins_header 2 DKIM-Signature hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in8 b/src/cleanup/cleanup_milter.in8
new file mode 100644
index 0000000..508b4ad
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in8
@@ -0,0 +1,7 @@
+open test-queue-file8.tmp
+
+ins_header 1 inserted-at-1 hya
+ins_header 2 inserted-at-2 hya
+ins_header 3 inserted-at-3 hya
+
+close
diff --git a/src/cleanup/cleanup_milter.in9 b/src/cleanup/cleanup_milter.in9
new file mode 100644
index 0000000..3bbdc3f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in9
@@ -0,0 +1,7 @@
+open test-queue-file9.tmp
+
+ins_header 1 inserted-at-1 hya
+ins_header 3 inserted-at-3 hya
+ins_header 5 inserted-at-5 hya
+
+close
diff --git a/src/cleanup/cleanup_milter.ref1 b/src/cleanup/cleanup_milter.ref1
new file mode 100644
index 0000000..a529c62
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref1
@@ -0,0 +1,56 @@
+*** ENVELOPE RECORDS test-queue-file.tmp ***
+ 0 message_size: 441 813 3 0 441
+ 81 message_arrival_time: Sat Jan 20 19:52:41 2007
+ 100 create_time: Sat Jan 20 19:52:47 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender: wietse@porcupine.org
+ 169 named_attribute: log_client_name=hades.porcupine.org
+ 206 named_attribute: log_client_address=168.100.189.10
+ 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10]
+ 297 named_attribute: log_helo_name=hades.porcupine.org
+ 332 named_attribute: log_protocol_name=SMTP
+ 356 named_attribute: client_name=hades.porcupine.org
+ 389 named_attribute: reverse_client_name=hades.porcupine.org
+ 430 named_attribute: client_address=168.100.189.10
+ 461 named_attribute: helo_name=hades.porcupine.org
+ 492 named_attribute: client_address_type=2
+ 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org
+ 558 original_recipient: wietse@porcupine.org
+ 580 recipient: wietse@porcupine.org
+ 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 650 original_recipient: alias@hades.porcupine.org
+ 677 canceled_recipient: wietse@porcupine.org
+ 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 747 original_recipient: alias@hades.porcupine.org
+ 774 canceled_recipient: root@porcupine.org
+ 794 pointer_record: 1258
+ 1258 original_recipient: xxxx
+ 1264 recipient: xxxx
+ 1270 pointer_record: 1287
+ 1287 original_recipient: yyyy
+ 1293 canceled_recipient: yyyy
+ 1299 pointer_record: 811
+ 811 *** MESSAGE CONTENTS test-queue-file.tmp ***
+ 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10])
+ 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405;
+ 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 989 pointer_record: 1316
+ 1316 pointer_record: 1367
+ 1367 pointer_record: 1333
+ 1333 pointer_record: 1460
+ 1460 regular_text: X: X-replaced-header replacement header text
+ 1506 pointer_record: 1401
+ 1401 regular_text: X2: test header value 3
+ 1426 regular_text: Y: 1234567
+ 1438 padding: 0
+ 1443 pointer_record: 1047
+ 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org>
+ 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 1154 regular_text: From: wietse@porcupine.org
+ 1182 regular_text: To: undisclosed-recipients:;
+ 1212 pointer_record: 0
+ 1229 regular_text:
+ 1231 regular_text: text
+ 1237 pointer_record: 0
+ 1254 *** HEADER EXTRACTED test-queue-file.tmp ***
+ 1256 *** MESSAGE FILE END test-queue-file.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref10a b/src/cleanup/cleanup_milter.ref10a
new file mode 100644
index 0000000..3f5c1cb
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref10a
@@ -0,0 +1,53 @@
+*** ENVELOPE RECORDS test-queue-file10.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file10.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 506
+ 506 regular_text:
+ 508 pointer_record: 573
+ 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1455 regular_text: pariatur?
+ 1467 regular_text:
+ 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2341 pointer_record: 531
+ 531 *** HEADER EXTRACTED test-queue-file10.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file10.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref10b b/src/cleanup/cleanup_milter.ref10b
new file mode 100644
index 0000000..3f5c1cb
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref10b
@@ -0,0 +1,53 @@
+*** ENVELOPE RECORDS test-queue-file10.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file10.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 506
+ 506 regular_text:
+ 508 pointer_record: 573
+ 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1455 regular_text: pariatur?
+ 1467 regular_text:
+ 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2341 pointer_record: 531
+ 531 *** HEADER EXTRACTED test-queue-file10.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file10.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref10c b/src/cleanup/cleanup_milter.ref10c
new file mode 100644
index 0000000..d920c6b
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref10c
@@ -0,0 +1,83 @@
+*** ENVELOPE RECORDS test-queue-file10.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file10.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 506
+ 506 regular_text:
+ 508 pointer_record: 573
+ 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1455 regular_text: pariatur?
+ 1467 regular_text:
+ 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2341 pointer_record: 2358
+ 2358 regular_text:
+ 2361 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 2426 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 2493 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 2561 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 2630 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 2699 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 2765 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 2833 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 2900 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 2965 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 3039 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 3108 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 3176 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 3243 regular_text: pariatur?
+ 3255 regular_text:
+ 3258 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 3323 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 3388 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 3456 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 3523 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 3594 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 3661 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 3728 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 3797 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 3867 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 3933 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 4002 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 4068 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 4129 pointer_record: 531
+ 531 *** HEADER EXTRACTED test-queue-file10.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file10.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref10d b/src/cleanup/cleanup_milter.ref10d
new file mode 100644
index 0000000..3f5c1cb
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref10d
@@ -0,0 +1,53 @@
+*** ENVELOPE RECORDS test-queue-file10.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file10.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 506
+ 506 regular_text:
+ 508 pointer_record: 573
+ 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1455 regular_text: pariatur?
+ 1467 regular_text:
+ 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2341 pointer_record: 531
+ 531 *** HEADER EXTRACTED test-queue-file10.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file10.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref10e b/src/cleanup/cleanup_milter.ref10e
new file mode 100644
index 0000000..490c622
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref10e
@@ -0,0 +1,89 @@
+*** ENVELOPE RECORDS test-queue-file10.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file10.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 573
+ 573 regular_text: foo1: foobar
+ 587 padding: 0
+ 590 pointer_record: 2392
+ 2392 regular_text: foo2: foobar
+ 2406 padding: 0
+ 2409 pointer_record: 506
+ 506 regular_text:
+ 508 pointer_record: 607
+ 607 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 672 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 739 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 807 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 876 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 945 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 1011 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1079 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1146 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1211 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1285 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1354 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1422 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1489 regular_text: pariatur?
+ 1501 regular_text:
+ 1504 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1569 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 1634 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 1702 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 1769 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 1840 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 1907 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 1974 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2043 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2113 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2179 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2248 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2314 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2375 pointer_record: 2426
+ 2426 regular_text:
+ 2429 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 2494 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 2561 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 2629 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 2698 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 2767 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 2833 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 2901 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 2968 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 3033 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 3107 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 3176 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 3244 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 3311 regular_text: pariatur?
+ 3323 regular_text:
+ 3326 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 3391 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 3456 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 3524 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 3591 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 3662 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 3729 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 3796 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 3865 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 3935 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 4001 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 4070 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 4136 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 4197 pointer_record: 531
+ 531 *** HEADER EXTRACTED test-queue-file10.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file10.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref11 b/src/cleanup/cleanup_milter.ref11
new file mode 100644
index 0000000..f8be5d4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref11
@@ -0,0 +1,66 @@
+*** ENVELOPE RECORDS test-queue-file11.tmp ***
+ 0 message_size: 366 605 1 0 366
+ 81 message_arrival_time: Mon Apr 27 20:41:30 2009
+ 100 create_time: Mon Apr 27 20:41:41 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender:
+ 149 pointer_record: 0
+ 164 named_attribute: log_client_name=localhost
+ 191 named_attribute: log_client_address=127.0.0.1
+ 221 named_attribute: log_client_port=51286
+ 244 named_attribute: log_message_origin=localhost[127.0.0.1]
+ 285 named_attribute: log_helo_name=localhost
+ 310 named_attribute: log_protocol_name=SMTP
+ 334 named_attribute: client_name=localhost
+ 357 named_attribute: reverse_client_name=localhost
+ 388 named_attribute: client_address=127.0.0.1
+ 414 named_attribute: client_port=51286
+ 433 named_attribute: helo_name=localhost
+ 454 named_attribute: protocol_name=SMTP
+ 474 named_attribute: client_address_type=2
+ 497 named_attribute: dsn_orig_rcpt=rfc822;wietse@localhost
+ 536 original_recipient: wietse@localhost
+ 554 recipient: wietse@localhost.porcupine.org
+ 586 pointer_record: 0
+ 603 *** MESSAGE CONTENTS test-queue-file11.tmp ***
+ 605 regular_text: Received: from localhost (localhost [127.0.0.1])
+ 655 regular_text: by hades.porcupine.org (Postfix) with SMTP id 382B12B3292
+ 715 regular_text: for <wietse@localhost>; Mon, 27 Apr 2009 20:41:30 -0400 (EDT)
+ 779 regular_text: Message-Id: <20090428004141.382B12B3292@hades.porcupine.org>
+ 841 regular_text: Date: Mon, 27 Apr 2009 20:41:30 -0400 (EDT)
+ 886 regular_text: From: MAILER-DAEMON
+ 907 regular_text: To: undisclosed-recipients:;
+ 937 pointer_record: 954
+ 954 pointer_record: 975
+ 975 regular_text:
+ 977 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+ 1042 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+ 1109 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae
+ 1177 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+ 1246 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+ 1315 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+ 1381 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+ 1449 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore
+ 1516 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+ 1581 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+ 1655 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+ 1724 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+ 1792 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+ 1859 regular_text: pariatur?
+ 1871 regular_text:
+ 1874 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui
+ 1939 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos
+ 2004 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non
+ 2072 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia
+ 2139 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+ 2210 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis
+ 2277 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime
+ 2344 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+ 2413 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+ 2483 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+ 2549 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+ 2618 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+ 2684 regular_text: consequatur aut perferendis doloribus asperiores repellat.
+ 2745 pointer_record: 971
+ 971 *** HEADER EXTRACTED test-queue-file11.tmp ***
+ 973 *** MESSAGE FILE END test-queue-file11.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref12 b/src/cleanup/cleanup_milter.ref12
new file mode 100644
index 0000000..d5d0f2c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref12
@@ -0,0 +1,31 @@
+*** ENVELOPE RECORDS test-queue-file12.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 *** MESSAGE CONTENTS test-queue-file12.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file12.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 573
+ 573 original_recipient: me@porcupine.org
+ 591 canceled_recipient: me@porcupine.org
+ 609 pointer_record: 626
+ 626 original_recipient: em@porcupine.org
+ 644 canceled_recipient: em@porcupine.org
+ 662 pointer_record: 571
+ 571 *** MESSAGE FILE END test-queue-file12.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13a b/src/cleanup/cleanup_milter.ref13a
new file mode 100644
index 0000000..3ccf028
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13a
@@ -0,0 +1,31 @@
+*** ENVELOPE RECORDS test-queue-file13a.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 *** MESSAGE CONTENTS test-queue-file13a.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13a.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 573
+ 573 original_recipient: me@porcupine.org
+ 591 canceled_recipient: me@porcupine.org
+ 609 pointer_record: 626
+ 626 original_recipient: em@porcupine.org
+ 644 canceled_recipient: em@porcupine.org
+ 662 pointer_record: 571
+ 571 *** MESSAGE FILE END test-queue-file13a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13b b/src/cleanup/cleanup_milter.ref13b
new file mode 100644
index 0000000..bb55fb6
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13b
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file13b.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 sender: m@porcupine.org
+ 590 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13b.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13b.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13c b/src/cleanup/cleanup_milter.ref13c
new file mode 100644
index 0000000..3ec0531
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13c
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file13c.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 pointer_record: 607
+ 607 sender: n@porcupine.org
+ 624 pointer_record: 590
+ 590 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13c.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13c.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13c.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13d b/src/cleanup/cleanup_milter.ref13d
new file mode 100644
index 0000000..df9f1dc
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13d
@@ -0,0 +1,39 @@
+*** ENVELOPE RECORDS test-queue-file13d.tmp ***
+ 0 message_size: 366 605 1 0 366
+ 81 message_arrival_time: Mon Apr 27 20:41:30 2009
+ 100 create_time: Mon Apr 27 20:41:41 2009
+ 124 named_attribute: rewrite_context=local
+ 147 pointer_record: 975
+ 975 pointer_record: 1009
+ 1009 sender: n@porcupine.org
+ 1026 pointer_record: 992
+ 992 pointer_record: 164
+ 164 named_attribute: log_client_name=localhost
+ 191 named_attribute: log_client_address=127.0.0.1
+ 221 named_attribute: log_client_port=51286
+ 244 named_attribute: log_message_origin=localhost[127.0.0.1]
+ 285 named_attribute: log_helo_name=localhost
+ 310 named_attribute: log_protocol_name=SMTP
+ 334 named_attribute: client_name=localhost
+ 357 named_attribute: reverse_client_name=localhost
+ 388 named_attribute: client_address=127.0.0.1
+ 414 named_attribute: client_port=51286
+ 433 named_attribute: helo_name=localhost
+ 454 named_attribute: protocol_name=SMTP
+ 474 named_attribute: client_address_type=2
+ 497 named_attribute: dsn_orig_rcpt=rfc822;wietse@localhost
+ 536 original_recipient: wietse@localhost
+ 554 recipient: wietse@localhost.porcupine.org
+ 586 pointer_record: 0
+ 603 *** MESSAGE CONTENTS test-queue-file13d.tmp ***
+ 605 regular_text: Received: from localhost (localhost [127.0.0.1])
+ 655 regular_text: by hades.porcupine.org (Postfix) with SMTP id 382B12B3292
+ 715 regular_text: for <wietse@localhost>; Mon, 27 Apr 2009 20:41:30 -0400 (EDT)
+ 779 regular_text: Message-Id: <20090428004141.382B12B3292@hades.porcupine.org>
+ 841 regular_text: Date: Mon, 27 Apr 2009 20:41:30 -0400 (EDT)
+ 886 regular_text: From: MAILER-DAEMON
+ 907 regular_text: To: undisclosed-recipients:;
+ 937 pointer_record: 0
+ 954 pointer_record: 0
+ 971 *** HEADER EXTRACTED test-queue-file13d.tmp ***
+ 973 *** MESSAGE FILE END test-queue-file13d.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13e b/src/cleanup/cleanup_milter.ref13e
new file mode 100644
index 0000000..495c7fa
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13e
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file13e.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 sender: m@porcupine.org
+ 590 named_attribute: notify_flags=1
+ 606 original_recipient: a@porcupine.org
+ 623 recipient: a@porcupine.org
+ 640 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13e.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13e.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13e.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13f b/src/cleanup/cleanup_milter.ref13f
new file mode 100644
index 0000000..dcc563a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13f
@@ -0,0 +1,32 @@
+*** ENVELOPE RECORDS test-queue-file13f.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 pointer_record: 657
+ 657 sender: n@porcupine.org
+ 674 pointer_record: 590
+ 590 named_attribute: notify_flags=1
+ 606 original_recipient: a@porcupine.org
+ 623 recipient: a@porcupine.org
+ 640 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13f.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13f.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13f.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13g b/src/cleanup/cleanup_milter.ref13g
new file mode 100644
index 0000000..acadf22
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13g
@@ -0,0 +1,34 @@
+*** ENVELOPE RECORDS test-queue-file13g.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 pointer_record: 657
+ 657 pointer_record: 691
+ 691 sender: o@porcupine.org
+ 708 pointer_record: 674
+ 674 pointer_record: 590
+ 590 named_attribute: notify_flags=1
+ 606 original_recipient: a@porcupine.org
+ 623 recipient: a@porcupine.org
+ 640 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13g.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13g.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13g.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13h b/src/cleanup/cleanup_milter.ref13h
new file mode 100644
index 0000000..c3a8fe9
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13h
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file13h.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 named_attribute: envelope_id=env-for-m
+ 596 named_attribute: ret_flags=2
+ 609 sender: m@porcupine.org
+ 626 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13h.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13h.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13h.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref13i b/src/cleanup/cleanup_milter.ref13i
new file mode 100644
index 0000000..006ef13
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref13i
@@ -0,0 +1,33 @@
+*** ENVELOPE RECORDS test-queue-file13i.tmp ***
+ 0 message_size: 332 182 1 0 332
+ 81 message_arrival_time: Sun Jan 21 13:32:59 2007
+ 100 create_time: Sun Jan 21 13:33:08 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 pointer_record: 573
+ 573 named_attribute: envelope_id=env-for-m
+ 596 named_attribute: ret_flags=2
+ 609 pointer_record: 643
+ 643 named_attribute: envelope_id=env-for-n
+ 666 named_attribute: ret_flags=1
+ 679 sender: n@porcupine.org
+ 696 pointer_record: 626
+ 626 pointer_record: 180
+ 180 *** MESSAGE CONTENTS test-queue-file13i.tmp ***
+ 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST)
+ 300 regular_text: From: me@porcupine.org
+ 324 regular_text: To: you@porcupine.org
+ 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 454 regular_text: Subject: hey!
+ 469 padding: 0
+ 472 pointer_record: 0
+ 489 regular_text:
+ 491 regular_text: text
+ 497 pointer_record: 0
+ 514 *** HEADER EXTRACTED test-queue-file13i.tmp ***
+ 516 original_recipient: you@porcupine.org
+ 535 recipient: you@porcupine.org
+ 554 pointer_record: 0
+ 571 *** MESSAGE FILE END test-queue-file13i.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14a1 b/src/cleanup/cleanup_milter.ref14a1
new file mode 100644
index 0000000..9006f9a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14a1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 message content rejected
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref14a2 b/src/cleanup/cleanup_milter.ref14a2
new file mode 100644
index 0000000..cb690d8
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14a2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file14a.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14a.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 pointer_record: 572
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14a.tmp ***
+ 623 pointer_record: 0
+ 640 *** MESSAGE FILE END test-queue-file14a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14b1 b/src/cleanup/cleanup_milter.ref14b1
new file mode 100644
index 0000000..5608b81
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14b1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: transport:nexthop:port
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref14b2 b/src/cleanup/cleanup_milter.ref14b2
new file mode 100644
index 0000000..209bf2b
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14b2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file14b.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14b.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 pointer_record: 572
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14b.tmp ***
+ 623 pointer_record: 677
+ 677 content_filter: transport:nexthop:port
+ 701 pointer_record: 640
+ 640 *** MESSAGE FILE END test-queue-file14b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14c1 b/src/cleanup/cleanup_milter.ref14c1
new file mode 100644
index 0000000..26b8ffd
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14c1
@@ -0,0 +1 @@
+./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: foo@examle.com
diff --git a/src/cleanup/cleanup_milter.ref14c2 b/src/cleanup/cleanup_milter.ref14c2
new file mode 100644
index 0000000..8d2be31
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14c2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file14c.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14c.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 pointer_record: 572
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14c.tmp ***
+ 623 pointer_record: 677
+ 677 redirect_to: foo@examle.com
+ 693 pointer_record: 640
+ 640 *** MESSAGE FILE END test-queue-file14c.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14d1 b/src/cleanup/cleanup_milter.ref14d1
new file mode 100644
index 0000000..2cea8d2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14d1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-discard: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>:
+./cleanup_milter: flags = discard_message
diff --git a/src/cleanup/cleanup_milter.ref14d2 b/src/cleanup/cleanup_milter.ref14d2
new file mode 100644
index 0000000..dece3d5
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14d2
@@ -0,0 +1,25 @@
+*** ENVELOPE RECORDS test-queue-file14d.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14d.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 0
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14d.tmp ***
+ 623 pointer_record: 0
+ 640 *** MESSAGE FILE END test-queue-file14d.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14e1 b/src/cleanup/cleanup_milter.ref14e1
new file mode 100644
index 0000000..d08c06f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14e1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-hold: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>:
+./cleanup_milter: flags = enable_header_body_filter hold_message enable_milters
diff --git a/src/cleanup/cleanup_milter.ref14e2 b/src/cleanup/cleanup_milter.ref14e2
new file mode 100644
index 0000000..e6e5cc0
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14e2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file14e.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14e.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 pointer_record: 572
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14e.tmp ***
+ 623 pointer_record: 0
+ 640 *** MESSAGE FILE END test-queue-file14e.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14f1 b/src/cleanup/cleanup_milter.ref14f1
new file mode 100644
index 0000000..9006f9a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14f1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 message content rejected
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref14f2 b/src/cleanup/cleanup_milter.ref14f2
new file mode 100644
index 0000000..3fdbf23
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14f2
@@ -0,0 +1,28 @@
+*** ENVELOPE RECORDS test-queue-file14f.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14f.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 regular_text: To: wietse@ahost.example.com
+ 690 pointer_record: 402
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 0
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14f.tmp ***
+ 623 pointer_record: 0
+ 640 *** MESSAGE FILE END test-queue-file14f.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref14g1 b/src/cleanup/cleanup_milter.ref14g1
new file mode 100644
index 0000000..9006f9a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14g1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 message content rejected
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref14g2 b/src/cleanup/cleanup_milter.ref14g2
new file mode 100644
index 0000000..1ee50e4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref14g2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file14g.tmp ***
+ 0 message_size: 365 256 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:05:19 2009
+ 100 create_time: Fri Jun 5 14:05:19 2009
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: wietse@ahost.example.com
+ 188 pointer_record: 0
+ 203 original_recipient: wietse
+ 211 recipient: wietse@ahost.example.com
+ 237 pointer_record: 0
+ 254 *** MESSAGE CONTENTS test-queue-file14g.tmp ***
+ 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 372 regular_text: To: wietse@ahost.example.com
+ 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com>
+ 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT)
+ 507 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 555 pointer_record: 642
+ 642 regular_text: X-SPAM-FLAG: YES
+ 660 pointer_record: 572
+ 572 regular_text:
+ 574 regular_text: Fri Jun 5 14:05:19 EDT 2009
+ 604 pointer_record: 0
+ 621 *** HEADER EXTRACTED test-queue-file14g.tmp ***
+ 623 pointer_record: 0
+ 640 *** MESSAGE FILE END test-queue-file14g.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15a1 b/src/cleanup/cleanup_milter.ref15a1
new file mode 100644
index 0000000..9006f9a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15a1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 message content rejected
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref15a2 b/src/cleanup/cleanup_milter.ref15a2
new file mode 100644
index 0000000..56c5d3e
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15a2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file15a.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15a.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15a.tmp ***
+ 588 pointer_record: 0
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15b1 b/src/cleanup/cleanup_milter.ref15b1
new file mode 100644
index 0000000..5608b81
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15b1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: transport:nexthop:port
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref15b2 b/src/cleanup/cleanup_milter.ref15b2
new file mode 100644
index 0000000..c38c0a3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15b2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file15b.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15b.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15b.tmp ***
+ 588 pointer_record: 676
+ 676 content_filter: transport:nexthop:port
+ 700 pointer_record: 605
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15c1 b/src/cleanup/cleanup_milter.ref15c1
new file mode 100644
index 0000000..26b8ffd
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15c1
@@ -0,0 +1 @@
+./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: foo@examle.com
diff --git a/src/cleanup/cleanup_milter.ref15c2 b/src/cleanup/cleanup_milter.ref15c2
new file mode 100644
index 0000000..7725f48
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15c2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file15c.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15c.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15c.tmp ***
+ 588 pointer_record: 676
+ 676 redirect_to: foo@examle.com
+ 692 pointer_record: 605
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15c.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15d1 b/src/cleanup/cleanup_milter.ref15d1
new file mode 100644
index 0000000..2cea8d2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15d1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-discard: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>:
+./cleanup_milter: flags = discard_message
diff --git a/src/cleanup/cleanup_milter.ref15d2 b/src/cleanup/cleanup_milter.ref15d2
new file mode 100644
index 0000000..746a13b
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15d2
@@ -0,0 +1,25 @@
+*** ENVELOPE RECORDS test-queue-file15d.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15d.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 0
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15d.tmp ***
+ 588 pointer_record: 0
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15d.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15e1 b/src/cleanup/cleanup_milter.ref15e1
new file mode 100644
index 0000000..d08c06f
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15e1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-hold: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>:
+./cleanup_milter: flags = enable_header_body_filter hold_message enable_milters
diff --git a/src/cleanup/cleanup_milter.ref15e2 b/src/cleanup/cleanup_milter.ref15e2
new file mode 100644
index 0000000..13f9cb4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15e2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file15e.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15e.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15e.tmp ***
+ 588 pointer_record: 0
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15e.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15f1 b/src/cleanup/cleanup_milter.ref15f1
new file mode 100644
index 0000000..b9d6021
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15f1
@@ -0,0 +1 @@
+./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: x@y.z
diff --git a/src/cleanup/cleanup_milter.ref15f2 b/src/cleanup/cleanup_milter.ref15f2
new file mode 100644
index 0000000..45dca53
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15f2
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file15f.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15f.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 regular_text: To: wietse@ahost.example.com
+ 689 pointer_record: 367
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 0
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15f.tmp ***
+ 588 pointer_record: 706
+ 706 redirect_to: x@y.z
+ 713 pointer_record: 605
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15f.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15g1 b/src/cleanup/cleanup_milter.ref15g1
new file mode 100644
index 0000000..295690a
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15g1
@@ -0,0 +1,2 @@
+./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: x:y:z
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref15g2 b/src/cleanup/cleanup_milter.ref15g2
new file mode 100644
index 0000000..fc67e56
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15g2
@@ -0,0 +1,30 @@
+*** ENVELOPE RECORDS test-queue-file15g.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15g.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 regular_text: To: wietse@ahost.example.com
+ 689 pointer_record: 367
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 0
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15g.tmp ***
+ 588 pointer_record: 706
+ 706 content_filter: x:y:z
+ 713 pointer_record: 605
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15g.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15h1 b/src/cleanup/cleanup_milter.ref15h1
new file mode 100644
index 0000000..bb51e0e
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15h1
@@ -0,0 +1,3 @@
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 whatever
+./cleanup_milter: ignoring: add_header X-SPAM-FLAG NO
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref15h2 b/src/cleanup/cleanup_milter.ref15h2
new file mode 100644
index 0000000..936f022
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15h2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file15h.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15h.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: YES
+ 659 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15h.tmp ***
+ 588 pointer_record: 0
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15h.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref15i1 b/src/cleanup/cleanup_milter.ref15i1
new file mode 100644
index 0000000..b561728
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15i1
@@ -0,0 +1,3 @@
+./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: NO from client_name[client_addr]; from=<sender> to=<recipient>: x:y:z
+./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: 5.7.1 whatever
+./cleanup_milter: errs = message content rejected
diff --git a/src/cleanup/cleanup_milter.ref15i2 b/src/cleanup/cleanup_milter.ref15i2
new file mode 100644
index 0000000..531ac4c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref15i2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file15i.tmp ***
+ 0 message_size: 365 221 1 0 365
+ 81 message_arrival_time: Fri Jun 5 14:06:34 2009
+ 99 create_time: Fri Jun 5 14:06:34 2009
+ 123 named_attribute: rewrite_context=local
+ 146 sender_fullname: Wietse Venema
+ 161 sender: wietse@ahost.example.com
+ 187 pointer_record: 0
+ 202 pointer_record: 0
+ 219 *** MESSAGE CONTENTS test-queue-file15i.tmp ***
+ 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001)
+ 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 337 regular_text: To: wietse@ahost.example.com
+ 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com>
+ 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT)
+ 472 regular_text: From: wietse@ahost.example.com (Wietse Venema)
+ 520 pointer_record: 641
+ 641 regular_text: X-SPAM-FLAG: NO
+ 658 pointer_record: 675
+ 675 regular_text: X-SPAM-FLAG: YES
+ 693 pointer_record: 537
+ 537 regular_text:
+ 539 regular_text: Fri Jun 5 14:06:34 EDT 2009
+ 569 pointer_record: 0
+ 586 *** HEADER EXTRACTED test-queue-file15i.tmp ***
+ 588 pointer_record: 0
+ 605 original_recipient: wietse
+ 613 recipient: wietse@ahost.example.com
+ 639 *** MESSAGE FILE END test-queue-file15i.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref16a1 b/src/cleanup/cleanup_milter.ref16a1
new file mode 100644
index 0000000..a1d30b0
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref16a1
@@ -0,0 +1,3 @@
+./cleanup_milter: NOQUEUE: milter-header-bcc: header X-SPAM-FLAG: NO from client_name[client_addr]; from=<sender> to=<recipient>: bar@example.com
+./cleanup_milter: NOQUEUE: milter-header-bcc: header X-SPAM-FLAG: YES from client_name[client_addr]; from=<sender> to=<recipient>: foo@example.com
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref16a2 b/src/cleanup/cleanup_milter.ref16a2
new file mode 100644
index 0000000..3b0edd2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref16a2
@@ -0,0 +1,37 @@
+*** ENVELOPE RECORDS test-queue-file16a.tmp ***
+ 0 message_size: 343 215 1 0 343 0
+ 97 message_arrival_time: Tue Nov 18 16:43:29 2014
+ 116 create_time: Tue Nov 18 16:43:29 2014
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 pointer_record: 654
+ 654 original_recipient: bar@example.com
+ 671 recipient: bar@example.com
+ 688 pointer_record: 739
+ 739 original_recipient: foo@example.com
+ 756 recipient: foo@example.com
+ 773 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file16a.tmp ***
+ 215 regular_text: Received: by host.example.com (Postfix, from userid 1001)
+ 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST)
+ 331 regular_text: To: user@example.com
+ 353 regular_text: Subject: test
+ 368 padding: 0
+ 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com>
+ 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST)
+ 476 regular_text: From: user@example.com (Wietse Venema)
+ 516 pointer_record: 705
+ 705 regular_text: X-SPAM-FLAG: NO
+ 722 pointer_record: 790
+ 790 regular_text: X-SPAM-FLAG: YES
+ 808 pointer_record: 533
+ 533 regular_text:
+ 535 regular_text: test
+ 541 pointer_record: 0
+ 558 *** HEADER EXTRACTED test-queue-file16a.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file16a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref16b1 b/src/cleanup/cleanup_milter.ref16b1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref16b1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref16b2 b/src/cleanup/cleanup_milter.ref16b2
new file mode 100644
index 0000000..2ae8719
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref16b2
@@ -0,0 +1,42 @@
+*** ENVELOPE RECORDS test-queue-file16b.tmp ***
+ 0 message_size: 343 215 1 0 343 0
+ 97 message_arrival_time: Tue Nov 18 16:43:29 2014
+ 116 create_time: Tue Nov 18 16:43:29 2014
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 pointer_record: 654
+ 654 named_attribute: notify_flags=1
+ 670 original_recipient: foo@example.com
+ 687 recipient: foo@example.com
+ 704 pointer_record: 721
+ 721 original_recipient: bar@example.com
+ 738 recipient: bar@example.com
+ 755 pointer_record: 772
+ 772 named_attribute: dsn_orig_rcpt=rfc822;orig-bar@example.com
+ 815 original_recipient: bar@example.com
+ 832 recipient: bar@example.com
+ 849 pointer_record: 866
+ 866 named_attribute: notify_flags=8
+ 882 original_recipient: bar@example.com
+ 899 recipient: bar@example.com
+ 916 pointer_record: 213
+ 213 *** MESSAGE CONTENTS test-queue-file16b.tmp ***
+ 215 regular_text: Received: by host.example.com (Postfix, from userid 1001)
+ 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST)
+ 331 regular_text: To: user@example.com
+ 353 regular_text: Subject: test
+ 368 padding: 0
+ 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com>
+ 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST)
+ 476 regular_text: From: user@example.com (Wietse Venema)
+ 516 pointer_record: 0
+ 533 regular_text:
+ 535 regular_text: test
+ 541 pointer_record: 0
+ 558 *** HEADER EXTRACTED test-queue-file16b.tmp ***
+ 560 pointer_record: 0
+ 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 616 original_recipient: user@example.com
+ 634 recipient: user@example.com
+ 652 *** MESSAGE FILE END test-queue-file16b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref2 b/src/cleanup/cleanup_milter.ref2
new file mode 100644
index 0000000..bdfc994
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref2
@@ -0,0 +1,36 @@
+*** ENVELOPE RECORDS test-queue-file2.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file2.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 pointer_record: 573
+ 573 pointer_record: 608
+ 608 pointer_record: 643
+ 643 regular_text: Subject: hey!
+ 658 padding: 0
+ 661 pointer_record: 489
+ 489 pointer_record: 678
+ 678 pointer_record: 712
+ 712 pointer_record: 746
+ 746 pointer_record: 780
+ 780 regular_text: foo: foobar
+ 793 padding: 0
+ 797 pointer_record: 695
+ 695 pointer_record: 506
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file2.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file2.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref3 b/src/cleanup/cleanup_milter.ref3
new file mode 100644
index 0000000..656b561
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref3
@@ -0,0 +1,50 @@
+*** ENVELOPE RECORDS test-queue-file3.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 573
+ 573 original_recipient: me@porcupine.org
+ 591 canceled_recipient: me@porcupine.org
+ 609 pointer_record: 1397
+ 1397 original_recipient: em@porcupine.org
+ 1415 canceled_recipient: em@porcupine.org
+ 1433 pointer_record: 197
+ 197 *** MESSAGE CONTENTS test-queue-file3.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 pointer_record: 842
+ 842 pointer_record: 1315
+ 1315 pointer_record: 1356
+ 1356 pointer_record: 1450
+ 1450 pointer_record: 1707
+ 1707 pointer_record: 1748
+ 1748 regular_text: From: me@porcupine.org
+ 1772 pointer_record: 1491
+ 1491 pointer_record: 1789
+ 1789 pointer_record: 1829
+ 1829 regular_text: To: you@porcupine.org
+ 1852 pointer_record: 1531
+ 1531 pointer_record: 1869
+ 1869 pointer_record: 1948
+ 1948 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 2010 pointer_record: 1610
+ 1610 pointer_record: 2027
+ 2027 pointer_record: 2089
+ 2089 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 2134 pointer_record: 1672
+ 1672 pointer_record: 2151
+ 2151 pointer_record: 2186
+ 2186 regular_text: Subject: hey!
+ 2201 padding: 0
+ 2204 pointer_record: 489
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file3.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file3.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref4 b/src/cleanup/cleanup_milter.ref4
new file mode 100644
index 0000000..4c3be60
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref4
@@ -0,0 +1,59 @@
+*** ENVELOPE RECORDS test-queue-file4.tmp ***
+ 0 message_size: 441 813 3 0 441
+ 81 message_arrival_time: Sat Jan 20 19:52:41 2007
+ 100 create_time: Sat Jan 20 19:52:47 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender: wietse@porcupine.org
+ 169 named_attribute: log_client_name=hades.porcupine.org
+ 206 named_attribute: log_client_address=168.100.189.10
+ 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10]
+ 297 named_attribute: log_helo_name=hades.porcupine.org
+ 332 named_attribute: log_protocol_name=SMTP
+ 356 named_attribute: client_name=hades.porcupine.org
+ 389 named_attribute: reverse_client_name=hades.porcupine.org
+ 430 named_attribute: client_address=168.100.189.10
+ 461 named_attribute: helo_name=hades.porcupine.org
+ 492 named_attribute: client_address_type=2
+ 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org
+ 558 original_recipient: wietse@porcupine.org
+ 580 recipient: wietse@porcupine.org
+ 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 650 original_recipient: alias@hades.porcupine.org
+ 677 recipient: wietse@porcupine.org
+ 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org
+ 747 original_recipient: alias@hades.porcupine.org
+ 774 recipient: root@porcupine.org
+ 794 pointer_record: 1258
+ 1258 original_recipient: 01
+ 1262 canceled_recipient: 01
+ 1266 pointer_record: 1283
+ 1283 original_recipient: 02
+ 1287 canceled_recipient: 02
+ 1291 pointer_record: 1308
+ 1308 original_recipient: 03
+ 1312 canceled_recipient: 03
+ 1316 pointer_record: 811
+ 811 *** MESSAGE CONTENTS test-queue-file4.tmp ***
+ 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10])
+ 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405;
+ 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 989 regular_text: X: 1
+ 995 padding: 0
+ 1006 regular_text: 2
+ 1010 regular_text: 3
+ 1014 regular_text: 4
+ 1018 regular_text: 5
+ 1022 regular_text: 6
+ 1026 regular_text: 7
+ 1030 regular_text: Y: 1234567
+ 1042 padding: 0
+ 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org>
+ 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST)
+ 1154 regular_text: From: wietse@porcupine.org
+ 1182 regular_text: To: undisclosed-recipients:;
+ 1212 pointer_record: 0
+ 1229 regular_text:
+ 1231 regular_text: text
+ 1237 pointer_record: 0
+ 1254 *** HEADER EXTRACTED test-queue-file4.tmp ***
+ 1256 *** MESSAGE FILE END test-queue-file4.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref5 b/src/cleanup/cleanup_milter.ref5
new file mode 100644
index 0000000..b5862a8
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref5
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file5.tmp ***
+ 0 message_size: 376 237 1 0 376
+ 81 message_arrival_time: Sun Jan 21 11:26:46 2007
+ 100 create_time: Sun Jan 21 11:26:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 original_recipient: you@porcupine.org
+ 199 recipient: you@porcupine.org
+ 218 pointer_record: 0
+ 235 *** MESSAGE CONTENTS test-queue-file5.tmp ***
+ 237 regular_text: Received: by hades.porcupine.org (Postfix, from userid 0)
+ 296 regular_text: id 38FA9290404; Sun, 21 Jan 2007 11:26:59 -0500 (EST)
+ 352 pointer_record: 707
+ 707 regular_text: Subject: hya
+ 721 padding: 0
+ 724 pointer_record: 662
+ 662 regular_text: X: whatevershebringswesing
+ 690 pointer_record: 394
+ 394 regular_text: Message-Id: <20070121162659.38FA9290404@hades.porcupine.org>
+ 456 regular_text: Date: Sun, 21 Jan 2007 11:26:46 -0500 (EST)
+ 501 regular_text: From: me@porcupine.org (Wietse Venema)
+ 541 regular_text: To: undisclosed-recipients:;
+ 571 pointer_record: 0
+ 588 regular_text:
+ 590 regular_text: text
+ 596 pointer_record: 0
+ 613 *** HEADER EXTRACTED test-queue-file5.tmp ***
+ 615 *** MESSAGE FILE END test-queue-file5.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref6a b/src/cleanup/cleanup_milter.ref6a
new file mode 100644
index 0000000..193960d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref6a
@@ -0,0 +1,28 @@
+*** ENVELOPE RECORDS test-queue-file6.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file6.tmp ***
+ 199 pointer_record: 573
+ 573 regular_text: X-Virus-Scanned: hya
+ 595 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 657 pointer_record: 261
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file6.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file6.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref6b b/src/cleanup/cleanup_milter.ref6b
new file mode 100644
index 0000000..fdcb38e
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref6b
@@ -0,0 +1,31 @@
+*** ENVELOPE RECORDS test-queue-file6.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file6.tmp ***
+ 199 pointer_record: 573
+ 573 regular_text: X-Virus-Scanned: hya
+ 595 pointer_record: 674
+ 674 regular_text: Domainkey-Signature: hya
+ 700 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 762 pointer_record: 657
+ 657 pointer_record: 261
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file6.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file6.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref6c b/src/cleanup/cleanup_milter.ref6c
new file mode 100644
index 0000000..9d58b6d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref6c
@@ -0,0 +1,33 @@
+*** ENVELOPE RECORDS test-queue-file6.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file6.tmp ***
+ 199 pointer_record: 573
+ 573 regular_text: X-Virus-Scanned: hya
+ 595 pointer_record: 779
+ 779 regular_text: DKIM-Signature: hya
+ 800 pointer_record: 674
+ 674 regular_text: Domainkey-Signature: hya
+ 700 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 762 pointer_record: 657
+ 657 pointer_record: 261
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file6.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file6.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref7 b/src/cleanup/cleanup_milter.ref7
new file mode 100644
index 0000000..ffc63a3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref7
@@ -0,0 +1,32 @@
+*** ENVELOPE RECORDS test-queue-file7.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file7.tmp ***
+ 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 pointer_record: 679
+ 679 regular_text: DKIM-Signature: hya
+ 700 pointer_record: 636
+ 636 regular_text: Domainkey-Signature: hya
+ 662 pointer_record: 573
+ 573 regular_text: X-Virus-Scanned: hya
+ 595 regular_text: From: me@porcupine.org
+ 619 pointer_record: 341
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file7.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file7.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref8 b/src/cleanup/cleanup_milter.ref8
new file mode 100644
index 0000000..5aadfd4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref8
@@ -0,0 +1,34 @@
+*** ENVELOPE RECORDS test-queue-file8.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file8.tmp ***
+ 199 pointer_record: 573
+ 573 regular_text: inserted-at-1: hya
+ 593 pointer_record: 672
+ 672 regular_text: inserted-at-2: hya
+ 692 pointer_record: 771
+ 771 regular_text: inserted-at-3: hya
+ 791 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 853 pointer_record: 754
+ 754 pointer_record: 655
+ 655 pointer_record: 261
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 regular_text: From: me@porcupine.org
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file8.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file8.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref9 b/src/cleanup/cleanup_milter.ref9
new file mode 100644
index 0000000..9cfd626
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref9
@@ -0,0 +1,33 @@
+*** ENVELOPE RECORDS test-queue-file9.tmp ***
+ 0 message_size: 332 199 1 0 332
+ 81 message_arrival_time: Sat Jan 20 20:53:54 2007
+ 100 create_time: Sat Jan 20 20:53:59 2007
+ 124 named_attribute: rewrite_context=local
+ 147 sender_fullname: Wietse Venema
+ 162 sender: me@porcupine.org
+ 180 pointer_record: 0
+ 197 *** MESSAGE CONTENTS test-queue-file9.tmp ***
+ 199 pointer_record: 573
+ 573 regular_text: inserted-at-1: hya
+ 593 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001)
+ 655 pointer_record: 261
+ 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST)
+ 317 pointer_record: 672
+ 672 regular_text: inserted-at-3: hya
+ 692 regular_text: From: me@porcupine.org
+ 716 pointer_record: 733
+ 733 regular_text: inserted-at-5: hya
+ 753 pointer_record: 341
+ 341 regular_text: To: you@porcupine.org
+ 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org>
+ 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT)
+ 471 regular_text: Subject: hey!
+ 486 padding: 0
+ 489 pointer_record: 0
+ 506 regular_text:
+ 508 regular_text: text
+ 514 pointer_record: 0
+ 531 *** HEADER EXTRACTED test-queue-file9.tmp ***
+ 533 original_recipient: you@porcupine.org
+ 552 recipient: you@porcupine.org
+ 571 *** MESSAGE FILE END test-queue-file9.tmp ***
diff --git a/src/cleanup/cleanup_milter.reg14a b/src/cleanup/cleanup_milter.reg14a
new file mode 100644
index 0000000..1a9ccf1
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14a
@@ -0,0 +1 @@
+/./ reject
diff --git a/src/cleanup/cleanup_milter.reg14b b/src/cleanup/cleanup_milter.reg14b
new file mode 100644
index 0000000..332ba69
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14b
@@ -0,0 +1 @@
+/./ FILTER transport:nexthop:port
diff --git a/src/cleanup/cleanup_milter.reg14c b/src/cleanup/cleanup_milter.reg14c
new file mode 100644
index 0000000..0421976
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14c
@@ -0,0 +1 @@
+/./ REDIRECT foo@examle.com
diff --git a/src/cleanup/cleanup_milter.reg14d b/src/cleanup/cleanup_milter.reg14d
new file mode 100644
index 0000000..78647a3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14d
@@ -0,0 +1 @@
+/./ DISCARD
diff --git a/src/cleanup/cleanup_milter.reg14e b/src/cleanup/cleanup_milter.reg14e
new file mode 100644
index 0000000..c88ee2c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14e
@@ -0,0 +1 @@
+/./ HOLD
diff --git a/src/cleanup/cleanup_milter.reg14f b/src/cleanup/cleanup_milter.reg14f
new file mode 100644
index 0000000..1a9ccf1
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14f
@@ -0,0 +1 @@
+/./ reject
diff --git a/src/cleanup/cleanup_milter.reg14g b/src/cleanup/cleanup_milter.reg14g
new file mode 100644
index 0000000..1a9ccf1
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg14g
@@ -0,0 +1 @@
+/./ reject
diff --git a/src/cleanup/cleanup_milter.reg15a b/src/cleanup/cleanup_milter.reg15a
new file mode 100644
index 0000000..1a9ccf1
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15a
@@ -0,0 +1 @@
+/./ reject
diff --git a/src/cleanup/cleanup_milter.reg15b b/src/cleanup/cleanup_milter.reg15b
new file mode 100644
index 0000000..332ba69
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15b
@@ -0,0 +1 @@
+/./ FILTER transport:nexthop:port
diff --git a/src/cleanup/cleanup_milter.reg15c b/src/cleanup/cleanup_milter.reg15c
new file mode 100644
index 0000000..0421976
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15c
@@ -0,0 +1 @@
+/./ REDIRECT foo@examle.com
diff --git a/src/cleanup/cleanup_milter.reg15d b/src/cleanup/cleanup_milter.reg15d
new file mode 100644
index 0000000..78647a3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15d
@@ -0,0 +1 @@
+/./ DISCARD
diff --git a/src/cleanup/cleanup_milter.reg15e b/src/cleanup/cleanup_milter.reg15e
new file mode 100644
index 0000000..c88ee2c
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15e
@@ -0,0 +1 @@
+/./ HOLD
diff --git a/src/cleanup/cleanup_milter.reg15f b/src/cleanup/cleanup_milter.reg15f
new file mode 100644
index 0000000..a629bf2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15f
@@ -0,0 +1 @@
+/./ redirect x@y.z
diff --git a/src/cleanup/cleanup_milter.reg15g b/src/cleanup/cleanup_milter.reg15g
new file mode 100644
index 0000000..0a25be3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15g
@@ -0,0 +1 @@
+/./ filter x:y:z
diff --git a/src/cleanup/cleanup_milter.reg15h b/src/cleanup/cleanup_milter.reg15h
new file mode 100644
index 0000000..8c97639
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15h
@@ -0,0 +1,2 @@
+/YES/ reject whatever
+/NO/ filter x:y:z
diff --git a/src/cleanup/cleanup_milter.reg15i b/src/cleanup/cleanup_milter.reg15i
new file mode 100644
index 0000000..8c97639
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg15i
@@ -0,0 +1,2 @@
+/YES/ reject whatever
+/NO/ filter x:y:z
diff --git a/src/cleanup/cleanup_milter.reg16a b/src/cleanup/cleanup_milter.reg16a
new file mode 100644
index 0000000..674aa5d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.reg16a
@@ -0,0 +1,2 @@
+/YES/ bcc foo@example.com
+/NO/ bcc bar@example.com
diff --git a/src/cleanup/cleanup_out.c b/src/cleanup/cleanup_out.c
new file mode 100644
index 0000000..8905fad
--- /dev/null
+++ b/src/cleanup/cleanup_out.c
@@ -0,0 +1,225 @@
+/*++
+/* NAME
+/* cleanup_out 3
+/* SUMMARY
+/* record output support
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* int CLEANUP_OUT_OK(state)
+/* CLEANUP_STATE *state;
+/*
+/* void cleanup_out(state, type, data, len)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *data;
+/* ssize_t len;
+/*
+/* void cleanup_out_string(state, type, str)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *str;
+/*
+/* void CLEANUP_OUT_BUF(state, type, buf)
+/* CLEANUP_STATE *state;
+/* int type;
+/* VSTRING *buf;
+/*
+/* void cleanup_out_format(state, type, format, ...)
+/* CLEANUP_STATE *state;
+/* int type;
+/* const char *format;
+/*
+/* void cleanup_out_header(state, buf)
+/* CLEANUP_STATE *state;
+/* VSTRING *buf;
+/* DESCRIPTION
+/* This module writes records to the output stream.
+/*
+/* CLEANUP_OUT_OK() is a macro that evaluates to non-zero
+/* as long as it makes sense to produce output. All output
+/* routines below check for this condition.
+/*
+/* cleanup_out() is the main record output routine. It writes
+/* one record of the specified type, with the specified data
+/* and length to the output stream.
+/*
+/* cleanup_out_string() outputs one string as a record.
+/*
+/* CLEANUP_OUT_BUF() is an unsafe macro that outputs
+/* one string buffer as a record.
+/*
+/* cleanup_out_format() formats its arguments and writes
+/* the result as a record.
+/*
+/* cleanup_out_header() outputs a multi-line header as records
+/* of the specified type. The input is expected to be newline
+/* separated (not newline terminated), and is modified.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <split_at.h>
+#include <stringops.h>
+
+/* Global library. */
+
+#include <record.h>
+#include <rec_type.h>
+#include <cleanup_user.h>
+#include <mail_params.h>
+#include <lex_822.h>
+#include <smtputf8.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+
+/* cleanup_out - output one single record */
+
+void cleanup_out(CLEANUP_STATE *state, int type, const char *string, ssize_t len)
+{
+ int err = 0;
+
+ /*
+ * Long message header lines have to be read and written as multiple
+ * records. Other header/body content, and envelope data, is copied one
+ * record at a time. Be sure to not skip a zero-length request.
+ *
+ * XXX We don't know if we're writing a message header or not, but that is
+ * not a problem. A REC_TYPE_NORM or REC_TYPE_CONT record can always be
+ * chopped up into an equivalent set of REC_TYPE_CONT plus REC_TYPE_NORM
+ * records.
+ */
+ if (CLEANUP_OUT_OK(state) == 0)
+ return;
+
+#define TEXT_RECORD(t) ((t) == REC_TYPE_NORM || (t) == REC_TYPE_CONT)
+
+ if (var_line_limit <= 0)
+ msg_panic("cleanup_out: bad line length limit: %d", var_line_limit);
+ do {
+ if (len > var_line_limit && TEXT_RECORD(type)) {
+ err = rec_put(state->dst, REC_TYPE_CONT, string, var_line_limit);
+ string += var_line_limit;
+ len -= var_line_limit;
+ } else {
+ err = rec_put(state->dst, type, string, len);
+ break;
+ }
+ } while (len > 0 && err >= 0);
+
+ if (err < 0) {
+ if (errno == EFBIG) {
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id);
+ state->errs |= CLEANUP_STAT_SIZE;
+ } else {
+ msg_warn("%s: write queue file: %m", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+ }
+}
+
+/* cleanup_out_string - output string to one single record */
+
+void cleanup_out_string(CLEANUP_STATE *state, int type, const char *string)
+{
+ cleanup_out(state, type, string, strlen(string));
+}
+
+/* cleanup_out_format - output one formatted record */
+
+void cleanup_out_format(CLEANUP_STATE *state, int type, const char *fmt,...)
+{
+ static VSTRING *vp;
+ va_list ap;
+
+ if (vp == 0)
+ vp = vstring_alloc(100);
+ va_start(ap, fmt);
+ vstring_vsprintf(vp, fmt, ap);
+ va_end(ap);
+ CLEANUP_OUT_BUF(state, type, vp);
+}
+
+/* cleanup_out_header - output one multi-line header as a bunch of records */
+
+void cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf)
+{
+ char *start = vstring_str(header_buf);
+ char *line;
+ char *next_line;
+ ssize_t line_len;
+
+ /*
+ * Fix 20140711: Auto-detect the presence of a non-ASCII header.
+ */
+ if (var_smtputf8_enable && *STR(header_buf) && !allascii(STR(header_buf))) {
+ state->smtputf8 |= SMTPUTF8_FLAG_HEADER;
+ /* Fix 20140713: request SMTPUTF8 support selectively. */
+ if (state->flags & CLEANUP_FLAG_AUTOUTF8)
+ state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
+ }
+
+ /*
+ * Prepend a tab to continued header lines that went through the address
+ * rewriting machinery. See cleanup_fold_header(state) below for the form
+ * of such header lines. NB: This code destroys the header. We could try
+ * to avoid clobbering it, but we're not going to use the data any
+ * further.
+ *
+ * XXX We prefer to truncate a header at the last line boundary before the
+ * header size limit. If this would undershoot the limit by more than
+ * 10%, we truncate between line boundaries to avoid losing too much
+ * text. This "unkind cut" may result in syntax errors and may trigger
+ * warnings from down-stream MTAs.
+ *
+ * If Milter is enabled, pad a short header record with a dummy record so
+ * that a header record can safely be overwritten by a pointer record.
+ * This simplifies header modification enormously.
+ */
+ for (line = start; line; line = next_line) {
+ next_line = split_at(line, '\n');
+ line_len = next_line ? next_line - 1 - line : strlen(line);
+ if (line + line_len > start + var_header_limit) {
+ if (line - start > 0.9 * var_header_limit) /* nice cut */
+ break;
+ start[var_header_limit] = 0; /* unkind cut */
+ next_line = 0;
+ }
+ if (line == start) {
+ cleanup_out_string(state, REC_TYPE_NORM, line);
+ if ((state->milters || cleanup_milters)
+ && line_len < REC_TYPE_PTR_PAYL_SIZE)
+ rec_pad(state->dst, REC_TYPE_DTXT,
+ REC_TYPE_PTR_PAYL_SIZE - line_len);
+ } else if (IS_SPACE_TAB(*line)) {
+ cleanup_out_string(state, REC_TYPE_NORM, line);
+ } else {
+ cleanup_out_format(state, REC_TYPE_NORM, "\t%s", line);
+ }
+ }
+}
diff --git a/src/cleanup/cleanup_out_recipient.c b/src/cleanup/cleanup_out_recipient.c
new file mode 100644
index 0000000..40735e2
--- /dev/null
+++ b/src/cleanup/cleanup_out_recipient.c
@@ -0,0 +1,265 @@
+/*++
+/* NAME
+/* cleanup_out_recipient 3
+/* SUMMARY
+/* envelope recipient output filter
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_out_recipient(state, dsn_orig_recipient,
+/* dsn_notify, orig_recipient,
+/* recipient)
+/* CLEANUP_STATE *state;
+/* const char *dsn_orig_recipient;
+/* const char *dsn_notify;
+/* const char *orig_recipient;
+/* const char *recipient;
+/* DESCRIPTION
+/* This module implements an envelope recipient output filter.
+/*
+/* cleanup_out_recipient() performs virtual table expansion
+/* and recipient duplicate filtering, and appends the
+/* resulting recipients to the output stream. It also
+/* generates DSN SUCCESS notifications.
+/*
+/* Arguments:
+/* .IP state
+/* Cleanup server state.
+/* .IP dsn_orig_recipient
+/* DSN original recipient information.
+/* .IP dsn_notify
+/* DSN notify flags.
+/* .IP orig_recipient
+/* Envelope recipient as received by Postfix.
+/* .IP recipient
+/* Envelope recipient as rewritten by Postfix.
+/* CONFIGURATION
+/* .ad
+/* .fi
+/* .IP enable_original_recipient
+/* Enable orig_recipient support.
+/* .IP local_duplicate_filter_limit
+/* Upper bound to the size of the recipient duplicate filter.
+/* Zero means no limit; this may cause the mail system to
+/* become stuck.
+/* .IP virtual_alias_maps
+/* list of virtual address lookup tables.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <msg.h>
+
+/* Global library. */
+
+#include <been_here.h>
+#include <mail_params.h>
+#include <rec_type.h>
+#include <ext_prop.h>
+#include <cleanup_user.h>
+#include <dsn_mask.h>
+#include <recipient_list.h>
+#include <dsn.h>
+#include <trace.h>
+#include <verify.h>
+#include <mail_queue.h> /* cleanup_trace_path */
+#include <mail_proto.h>
+#include <msg_stats.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_trace_append - update trace logfile */
+
+static void cleanup_trace_append(CLEANUP_STATE *state, RECIPIENT *rcpt,
+ DSN *dsn)
+{
+ MSG_STATS stats;
+
+ if (cleanup_trace_path == 0) {
+ cleanup_trace_path = vstring_alloc(10);
+ mail_queue_path(cleanup_trace_path, MAIL_QUEUE_TRACE,
+ state->queue_id);
+ }
+ if (trace_append(BOUNCE_FLAG_CLEAN, state->queue_id,
+ CLEANUP_MSG_STATS(&stats, state),
+ rcpt, "none", dsn) != 0) {
+ msg_warn("%s: trace logfile update error", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+}
+
+/* cleanup_verify_append - update verify daemon */
+
+static void cleanup_verify_append(CLEANUP_STATE *state, RECIPIENT *rcpt,
+ DSN *dsn, int verify_status)
+{
+ MSG_STATS stats;
+
+ if (verify_append(state->queue_id, CLEANUP_MSG_STATS(&stats, state),
+ rcpt, "none", dsn, verify_status) != 0) {
+ msg_warn("%s: verify service update error", state->queue_id);
+ state->errs |= CLEANUP_STAT_WRITE;
+ }
+}
+
+/* cleanup_out_recipient - envelope recipient output filter */
+
+void cleanup_out_recipient(CLEANUP_STATE *state,
+ const char *dsn_orcpt,
+ int dsn_notify,
+ const char *orcpt,
+ const char *recip)
+{
+ ARGV *argv;
+ char **cpp;
+
+ /*
+ * XXX Not elegant, but eliminates complexity in the record reading loop.
+ */
+ if (dsn_orcpt == 0)
+ dsn_orcpt = "";
+
+ /*
+ * Distinguish between different original recipient addresses that map
+ * onto the same mailbox. The recipient will use our original recipient
+ * message header to figure things out.
+ *
+ * Postfix 2.2 compatibility: when ignoring differences in Postfix original
+ * recipient information, also ignore differences in DSN attributes. We
+ * do, however, keep the DSN attributes of the recipient that survives
+ * duplicate elimination.
+ */
+#define STREQ(x, y) (strcmp((x), (y)) == 0)
+
+ if ((state->flags & CLEANUP_FLAG_MAP_OK) == 0
+ || cleanup_virt_alias_maps == 0) {
+ if ((var_enable_orcpt ?
+ been_here(state->dups, "%s\n%d\n%s\n%s",
+ dsn_orcpt, dsn_notify, orcpt, recip) :
+ been_here_fixed(state->dups, recip)) == 0) {
+ if (dsn_notify)
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ if (*dsn_orcpt)
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ORCPT, dsn_orcpt);
+ cleanup_out_string(state, REC_TYPE_ORCP, orcpt);
+ cleanup_out_string(state, REC_TYPE_RCPT, recip);
+ state->rcpt_count++;
+ }
+ }
+
+ /*
+ * XXX DSN. RFC 3461 gives us three options for multi-recipient aliases
+ * (we're treating single recipient aliases as a special case of
+ * multi-recipient aliases, one argument being that it is none of the
+ * sender's business).
+ *
+ * (a) Don't propagate ENVID, NOTIFY, RET, or ORCPT. If NOTIFY specified
+ * SUCCESS, send a "relayed" DSN.
+ *
+ * (b) Propagate ENVID, (NOTIFY minus SUCCESS), RET, and ORCPT. If NOTIFY
+ * specified SUCCESS, send an "expanded" DSN.
+ *
+ * (c) Propagate ENVID, NOTIFY, RET, and ORCPT to one recipient only. Send
+ * no DSN.
+ *
+ * In all three cases we are modifying at least one NOTIFY value. Either we
+ * have to record explicit dsn_notify records, or we must not allow the
+ * use of a per-message non-default NOTIFY value that applies to all
+ * recipient records.
+ *
+ * Alternatives (a) and (c) require that we store explicit per-recipient RET
+ * and ENVID records, at least for the recipients that are excluded from
+ * RET and ENVID propagation. This means storing explicit ENVID records
+ * to indicate that the information does not exist. All this makes
+ * alternative (b) more and more attractive. It is no surprise that we
+ * use (b) here and in the local delivery agent.
+ *
+ * In order to generate a SUCCESS notification from the cleanup server we
+ * have to write the trace logfile record now. We're NOT going to flush
+ * the trace file from the cleanup server; if we need to write bounce
+ * logfile records, and the bounce service fails, we must be able to
+ * cancel the entire cleanup request including any success or failure
+ * notifications. The queue manager will flush the trace (and bounce)
+ * logfile, possibly after it has generated its own success or failure
+ * notification records.
+ *
+ * Postfix 2.2 compatibility: when ignoring differences in Postfix original
+ * recipient information, also ignore differences in DSN attributes. We
+ * do, however, keep the DSN attributes of the recipient that survives
+ * duplicate elimination.
+ *
+ * In the case of a verify(8) request for a one-to-many alias, declare the
+ * alias address as "deliverable". Do not verify the individual addresses
+ * in the expansion because that results in multiple verify(8) updates
+ * for one verify(8) request.
+ *
+ * Multiple verify(8) updates for one verify(8) request would overwrite
+ * each other's status, and if the last status update is "undeliverable",
+ * then the whole alias is flagged as undeliverable.
+ */
+ else {
+ RECIPIENT rcpt;
+ DSN dsn;
+
+ argv = cleanup_map1n_internal(state, recip, cleanup_virt_alias_maps,
+ cleanup_ext_prop_mask & EXT_PROP_VIRTUAL);
+ if (argv->argc > 1 && (state->tflags & DEL_REQ_FLAG_MTA_VRFY)) {
+ (void) DSN_SIMPLE(&dsn, "2.0.0", "aliased to multiple recipients");
+ dsn.action = "deliverable";
+ RECIPIENT_ASSIGN(&rcpt, 0, dsn_orcpt, dsn_notify, orcpt, recip);
+ cleanup_verify_append(state, &rcpt, &dsn, DEL_RCPT_STAT_OK);
+ argv_free(argv);
+ return;
+ }
+ if ((dsn_notify & DSN_NOTIFY_SUCCESS)
+ && (argv->argc > 1 || strcmp(recip, argv->argv[0]) != 0)) {
+ (void) DSN_SIMPLE(&dsn, "2.0.0", "alias expanded");
+ dsn.action = "expanded";
+ RECIPIENT_ASSIGN(&rcpt, 0, dsn_orcpt, dsn_notify, orcpt, recip);
+ cleanup_trace_append(state, &rcpt, &dsn);
+ dsn_notify = (dsn_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER :
+ dsn_notify & ~DSN_NOTIFY_SUCCESS);
+ }
+ for (cpp = argv->argv; *cpp; cpp++) {
+ if ((var_enable_orcpt ?
+ been_here(state->dups, "%s\n%d\n%s\n%s",
+ dsn_orcpt, dsn_notify, orcpt, *cpp) :
+ been_here_fixed(state->dups, *cpp)) == 0) {
+ if (dsn_notify)
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%d",
+ MAIL_ATTR_DSN_NOTIFY, dsn_notify);
+ if (*dsn_orcpt)
+ cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_DSN_ORCPT, dsn_orcpt);
+ cleanup_out_string(state, REC_TYPE_ORCP, orcpt);
+ cleanup_out_string(state, REC_TYPE_RCPT, *cpp);
+ state->rcpt_count++;
+ }
+ }
+ argv_free(argv);
+ }
+}
diff --git a/src/cleanup/cleanup_region.c b/src/cleanup/cleanup_region.c
new file mode 100644
index 0000000..b2acbee
--- /dev/null
+++ b/src/cleanup/cleanup_region.c
@@ -0,0 +1,232 @@
+/*++
+/* NAME
+/* cleanup_region 3
+/* SUMMARY
+/* queue file region manager
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* void cleanup_region_init(state)
+/* CLEANUP_STATE *state;
+/*
+/* CLEANUP_REGION *cleanup_region_open(state, space_needed)
+/* CLEANUP_STATE *state;
+/* ssize_t space_needed;
+/*
+/* int cleanup_region_close(state, rp)
+/* CLEANUP_STATE *state;
+/* CLEANUP_REGION *rp;
+/*
+/* CLEANUP_REGION *cleanup_region_return(state, rp)
+/* CLEANUP_STATE *state;
+/* CLEANUP_REGION *rp;
+/*
+/* void cleanup_region_done(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* This module maintains queue file regions. Regions are created
+/* on-the-fly and can be reused multiple times. Each region
+/* structure consists of a file offset, a length (0 for an
+/* open-ended region at the end of the file), a write offset
+/* (maintained by the caller), and list linkage. Region
+/* boundaries are not enforced by this module. It is up to the
+/* caller to ensure that they stay within bounds.
+/*
+/* cleanup_region_init() performs mandatory initialization and
+/* overlays an initial region structure over an already existing
+/* queue file. This function must not be called before the
+/* queue file is complete.
+/*
+/* cleanup_region_open() opens an existing region or creates
+/* a new region that can accommodate at least the specified
+/* amount of space. A new region is an open-ended region at
+/* the end of the file; it must be closed (see next) before
+/* unrelated data can be appended to the same file.
+/*
+/* cleanup_region_close() indicates that a region will not be
+/* updated further. With an open-ended region, the region's
+/* end is frozen just before the caller-maintained write offset.
+/* With a close-ended region, unused space (beginning at the
+/* caller-maintained write offset) may be returned to the free
+/* pool.
+/*
+/* cleanup_region_return() returns a list of regions to the
+/* free pool, and returns a null pointer. To avoid fragmentation,
+/* adjacent free regions may be coalesced together.
+/*
+/* cleanup_region_done() destroys all in-memory information
+/* that was allocated for administering queue file regions.
+/*
+/* Arguments:
+/* .IP state
+/* Queue file and message processing state. This state is
+/* updated as records are processed and as errors happen.
+/* .IP space_needed
+/* The minimum region size needed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <warn_stat.h>
+
+/* Application-specific. */
+
+#include <cleanup.h>
+
+/* cleanup_region_alloc - create queue file region */
+
+static CLEANUP_REGION *cleanup_region_alloc(off_t start, off_t len)
+{
+ CLEANUP_REGION *rp;
+
+ rp = (CLEANUP_REGION *) mymalloc(sizeof(*rp));
+ rp->write_offs = rp->start = start;
+ rp->len = len;
+ rp->next = 0;
+
+ return (rp);
+}
+
+/* cleanup_region_free - destroy region list */
+
+static CLEANUP_REGION *cleanup_region_free(CLEANUP_REGION *regions)
+{
+ CLEANUP_REGION *rp;
+ CLEANUP_REGION *next;
+
+ for (rp = regions; rp != 0; rp = next) {
+ next = rp->next;
+ myfree((void *) rp);
+ }
+ return (0);
+}
+
+/* cleanup_region_init - create initial region overlay */
+
+void cleanup_region_init(CLEANUP_STATE *state)
+{
+ const char *myname = "cleanup_region_init";
+
+ /*
+ * Sanity check.
+ */
+ if (state->free_regions != 0 || state->body_regions != 0)
+ msg_panic("%s: repeated call", myname);
+
+ /*
+ * Craft the first regions on the fly, from circumstantial evidence.
+ */
+ state->body_regions =
+ cleanup_region_alloc(state->append_hdr_pt_target,
+ state->xtra_offset - state->append_hdr_pt_target);
+ if (msg_verbose)
+ msg_info("%s: body start %ld len %ld",
+ myname, (long) state->body_regions->start, (long) state->body_regions->len);
+}
+
+/* cleanup_region_open - open existing region or create new region */
+
+CLEANUP_REGION *cleanup_region_open(CLEANUP_STATE *state, ssize_t len)
+{
+ const char *myname = "cleanup_region_open";
+ CLEANUP_REGION **rpp;
+ CLEANUP_REGION *rp;
+ struct stat st;
+
+ /*
+ * Find the first region that is large enough, or create a new region.
+ */
+ for (rpp = &state->free_regions; /* see below */ ; rpp = &(rp->next)) {
+
+ /*
+ * Create an open-ended region at the end of the queue file. We
+ * freeze the region size after we stop writing to it. XXX Assume
+ * that fstat() returns a file size that is never less than the file
+ * append offset. It is not a problem if fstat() returns a larger
+ * result; we would just waste some space.
+ */
+ if ((rp = *rpp) == 0) {
+ if (fstat(vstream_fileno(state->dst), &st) < 0)
+ msg_fatal("%s: fstat file %s: %m", myname, cleanup_path);
+ rp = cleanup_region_alloc(st.st_size, 0);
+ break;
+ }
+
+ /*
+ * Reuse an existing region.
+ */
+ if (rp->len >= len) {
+ (*rpp) = rp->next;
+ rp->next = 0;
+ rp->write_offs = rp->start;
+ break;
+ }
+
+ /*
+ * Skip a too small region.
+ */
+ if (msg_verbose)
+ msg_info("%s: skip start %ld len %ld < %ld",
+ myname, (long) rp->start, (long) rp->len, (long) len);
+ }
+ if (msg_verbose)
+ msg_info("%s: done start %ld len %ld",
+ myname, (long) rp->start, (long) rp->len);
+ return (rp);
+}
+
+/* cleanup_region_close - freeze queue file region size */
+
+void cleanup_region_close(CLEANUP_STATE *unused_state, CLEANUP_REGION *rp)
+{
+ const char *myname = "cleanup_region_close";
+
+ /*
+ * If this region is still open ended, freeze the size. If this region is
+ * closed, some future version of this routine may shrink the size and
+ * return the unused portion to the free pool.
+ */
+ if (rp->len == 0)
+ rp->len = rp->write_offs - rp->start;
+ if (msg_verbose)
+ msg_info("%s: freeze start %ld len %ld",
+ myname, (long) rp->start, (long) rp->len);
+}
+
+/* cleanup_region_return - return region list to free pool */
+
+CLEANUP_REGION *cleanup_region_return(CLEANUP_STATE *state, CLEANUP_REGION *rp)
+{
+ CLEANUP_REGION **rpp;
+
+ for (rpp = &state->free_regions; (*rpp) != 0; rpp = &(*rpp)->next)
+ /* void */ ;
+ *rpp = rp;
+ return (0);
+}
+
+/* cleanup_region_done - destroy region metadata */
+
+void cleanup_region_done(CLEANUP_STATE *state)
+{
+ if (state->free_regions != 0)
+ state->free_regions = cleanup_region_free(state->free_regions);
+ if (state->body_regions != 0)
+ state->body_regions = cleanup_region_free(state->body_regions);
+}
diff --git a/src/cleanup/cleanup_rewrite.c b/src/cleanup/cleanup_rewrite.c
new file mode 100644
index 0000000..3c81e7b
--- /dev/null
+++ b/src/cleanup/cleanup_rewrite.c
@@ -0,0 +1,123 @@
+/*++
+/* NAME
+/* cleanup_rewrite 3
+/* SUMMARY
+/* address canonicalization
+/* SYNOPSIS
+/* #include <cleanup.h>
+/*
+/* int cleanup_rewrite_external(context_name, result, addr)
+/* const char *context;
+/* VSTRING *result;
+/* const char *addr;
+/*
+/* int cleanup_rewrite_internal(context_name, result, addr)
+/* const char *context;
+/* VSTRING *result;
+/* const char *addr;
+/*
+/* int cleanup_rewrite_tree(context_name, tree)
+/* const char *context;
+/* TOK822 *tree;
+/* DESCRIPTION
+/* This module rewrites addresses to canonical form, adding missing
+/* domains and stripping source routes etc., and performs
+/* \fIcanonical\fR map lookups to map addresses to official form.
+/* These functions return non-zero when the address was changed.
+/*
+/* cleanup_rewrite_init() performs one-time initialization.
+/*
+/* cleanup_rewrite_external() rewrites the external (quoted) string
+/* form of an address.
+/*
+/* cleanup_rewrite_internal() is a wrapper around the
+/* cleanup_rewrite_external() routine that transforms from
+/* internal (quoted) string form to external form and back.
+/*
+/* cleanup_rewrite_tree() is a wrapper around the
+/* cleanup_rewrite_external() routine that transforms from
+/* internal parse tree form to external form and back.
+/*
+/* Arguments:
+/* .IP context_name
+/* The name of an address rewriting context that supplies
+/* the equivalents of myorigin and mydomain.
+/* .IP result
+/* Result buffer.
+/* .IP addr
+/* Input buffer.
+/* DIAGNOSTICS
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <tok822.h>
+#include <rewrite_clnt.h>
+#include <quote_822_local.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+#define STR vstring_str
+
+/* cleanup_rewrite_external - rewrite address external form */
+
+int cleanup_rewrite_external(const char *context_name, VSTRING *result,
+ const char *addr)
+{
+ rewrite_clnt(context_name, addr, result);
+ return (strcmp(STR(result), addr) != 0);
+}
+
+/* cleanup_rewrite_tree - rewrite address node */
+
+int cleanup_rewrite_tree(const char *context_name, TOK822 *tree)
+{
+ VSTRING *dst = vstring_alloc(100);
+ VSTRING *src = vstring_alloc(100);
+ int did_rewrite;
+
+ tok822_externalize(src, tree->head, TOK822_STR_DEFL);
+ did_rewrite = cleanup_rewrite_external(context_name, dst, STR(src));
+ tok822_free_tree(tree->head);
+ tree->head = tok822_scan(STR(dst), &tree->tail);
+ vstring_free(dst);
+ vstring_free(src);
+ return (did_rewrite);
+}
+
+/* cleanup_rewrite_internal - rewrite address internal form */
+
+int cleanup_rewrite_internal(const char *context_name,
+ VSTRING *result, const char *addr)
+{
+ VSTRING *dst = vstring_alloc(100);
+ VSTRING *src = vstring_alloc(100);
+ int did_rewrite;
+
+ quote_822_local(src, addr);
+ did_rewrite = cleanup_rewrite_external(context_name, dst, STR(src));
+ unquote_822_local(result, STR(dst));
+ vstring_free(dst);
+ vstring_free(src);
+ return (did_rewrite);
+}
diff --git a/src/cleanup/cleanup_state.c b/src/cleanup/cleanup_state.c
new file mode 100644
index 0000000..99adf84
--- /dev/null
+++ b/src/cleanup/cleanup_state.c
@@ -0,0 +1,200 @@
+/*++
+/* NAME
+/* cleanup_state 3
+/* SUMMARY
+/* per-message state variables
+/* SYNOPSIS
+/* #include "cleanup.h"
+/*
+/* CLEANUP_STATE *cleanup_state_alloc(src)
+/* VSTREAM *src;
+/*
+/* void cleanup_state_free(state)
+/* CLEANUP_STATE *state;
+/* DESCRIPTION
+/* This module maintains about two dozen state variables
+/* that are used by many routines in the course of processing one
+/* message.
+/*
+/* cleanup_state_alloc() initializes the per-message state variables.
+/*
+/* cleanup_state_free() cleans up.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <been_here.h>
+#include <mail_params.h>
+#include <mime_state.h>
+#include <mail_proto.h>
+
+/* Milter library. */
+
+#include <milter.h>
+
+/* Application-specific. */
+
+#include "cleanup.h"
+
+/* cleanup_state_alloc - initialize global state */
+
+CLEANUP_STATE *cleanup_state_alloc(VSTREAM *src)
+{
+ CLEANUP_STATE *state = (CLEANUP_STATE *) mymalloc(sizeof(*state));
+
+ state->attr_buf = vstring_alloc(10);
+ state->temp1 = vstring_alloc(10);
+ state->temp2 = vstring_alloc(10);
+ if (cleanup_strip_chars)
+ state->stripped_buf = vstring_alloc(10);
+ state->src = src;
+ state->dst = 0;
+ state->handle = 0;
+ state->queue_name = 0;
+ state->queue_id = 0;
+ state->arrival_time.tv_sec = state->arrival_time.tv_usec = 0;
+ state->fullname = 0;
+ state->sender = 0;
+ state->recip = 0;
+ state->orig_rcpt = 0;
+ state->return_receipt = 0;
+ state->errors_to = 0;
+ state->auto_hdrs = argv_alloc(1);
+ state->hbc_rcpt = 0;
+ state->flags = 0;
+ state->tflags = 0;
+ state->qmgr_opts = 0;
+ state->errs = 0;
+ state->err_mask = 0;
+ state->headers_seen = 0;
+ state->hop_count = 0;
+ state->resent = "";
+ state->dups = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD);
+ state->action = cleanup_envelope;
+ state->data_offset = -1;
+ state->body_offset = -1;
+ state->xtra_offset = -1;
+ state->cont_length = 0;
+ state->sender_pt_offset = -1;
+ state->sender_pt_target = -1;
+ state->append_rcpt_pt_offset = -1;
+ state->append_rcpt_pt_target = -1;
+ state->append_hdr_pt_offset = -1;
+ state->append_hdr_pt_target = -1;
+ state->append_meta_pt_offset = -1;
+ state->append_meta_pt_target = -1;
+ state->rcpt_count = 0;
+ state->reason = 0;
+ state->smtp_reply = 0;
+ state->attr = nvtable_create(10);
+ nvtable_update(state->attr, MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL);
+ state->mime_state = 0;
+ state->mime_errs = 0;
+ state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL;
+ state->filter = 0;
+ state->redirect = 0;
+ state->dsn_envid = 0;
+ state->dsn_ret = 0;
+ state->dsn_notify = 0;
+ state->dsn_orcpt = 0;
+ state->verp_delims = 0;
+ state->milters = 0;
+ state->client_name = 0;
+ state->reverse_name = 0;
+ state->client_addr = 0;
+ state->client_af = 0;
+ state->client_port = 0;
+ state->server_addr = 0;
+ state->server_port = 0;
+ state->milter_ext_from = 0;
+ state->milter_ext_rcpt = 0;
+ state->milter_err_text = 0;
+ state->milter_dsn_buf = 0;
+ state->free_regions = state->body_regions = state->curr_body_region = 0;
+ state->smtputf8 = 0;
+ return (state);
+}
+
+/* cleanup_state_free - destroy global state */
+
+void cleanup_state_free(CLEANUP_STATE *state)
+{
+ vstring_free(state->attr_buf);
+ vstring_free(state->temp1);
+ vstring_free(state->temp2);
+ if (cleanup_strip_chars)
+ vstring_free(state->stripped_buf);
+ if (state->fullname)
+ myfree(state->fullname);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->recip)
+ myfree(state->recip);
+ if (state->orig_rcpt)
+ myfree(state->orig_rcpt);
+ if (state->return_receipt)
+ myfree(state->return_receipt);
+ if (state->errors_to)
+ myfree(state->errors_to);
+ argv_free(state->auto_hdrs);
+ if (state->hbc_rcpt)
+ argv_free(state->hbc_rcpt);
+ if (state->queue_name)
+ myfree(state->queue_name);
+ if (state->queue_id)
+ myfree(state->queue_id);
+ been_here_free(state->dups);
+ if (state->reason)
+ myfree(state->reason);
+ if (state->smtp_reply)
+ myfree(state->smtp_reply);
+ nvtable_free(state->attr);
+ if (state->mime_state)
+ mime_state_free(state->mime_state);
+ if (state->filter)
+ myfree(state->filter);
+ if (state->redirect)
+ myfree(state->redirect);
+ if (state->dsn_envid)
+ myfree(state->dsn_envid);
+ if (state->dsn_orcpt)
+ myfree(state->dsn_orcpt);
+ if (state->verp_delims)
+ myfree(state->verp_delims);
+ if (state->milters)
+ milter_free(state->milters);
+ if (state->milter_ext_from)
+ vstring_free(state->milter_ext_from);
+ if (state->milter_ext_rcpt)
+ vstring_free(state->milter_ext_rcpt);
+ if (state->milter_err_text)
+ vstring_free(state->milter_err_text);
+ if (state->milter_dsn_buf)
+ vstring_free(state->milter_dsn_buf);
+ cleanup_region_done(state);
+ myfree((void *) state);
+}
diff --git a/src/cleanup/loremipsum b/src/cleanup/loremipsum
new file mode 100644
index 0000000..774f86d
--- /dev/null
+++ b/src/cleanup/loremipsum
@@ -0,0 +1,28 @@
+Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+quae ab illo inventore veritatis et quasi architecto beatae vitae
+dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+velit, sed quia non numquam eius modi tempora incidunt ut labore
+et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+pariatur?
+
+At vero eos et accusamus et iusto odio dignissimos ducimus qui
+blanditiis praesentium voluptatum deleniti atque corrupti quos
+dolores et quas molestias excepturi sint occaecati cupiditate non
+provident, similique sunt in culpa qui officia deserunt mollitia
+animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+est et expedita distinctio. Nam libero tempore, cum soluta nobis
+est eligendi optio cumque nihil impedit quo minus id quod maxime
+placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+consequatur aut perferendis doloribus asperiores repellat.
diff --git a/src/cleanup/loremipsum2 b/src/cleanup/loremipsum2
new file mode 100644
index 0000000..93cfa67
--- /dev/null
+++ b/src/cleanup/loremipsum2
@@ -0,0 +1,57 @@
+Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+quae ab illo inventore veritatis et quasi architecto beatae vitae
+dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+velit, sed quia non numquam eius modi tempora incidunt ut labore
+et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+pariatur?
+
+At vero eos et accusamus et iusto odio dignissimos ducimus qui
+blanditiis praesentium voluptatum deleniti atque corrupti quos
+dolores et quas molestias excepturi sint occaecati cupiditate non
+provident, similique sunt in culpa qui officia deserunt mollitia
+animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+est et expedita distinctio. Nam libero tempore, cum soluta nobis
+est eligendi optio cumque nihil impedit quo minus id quod maxime
+placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+consequatur aut perferendis doloribus asperiores repellat.
+
+Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
+quae ab illo inventore veritatis et quasi architecto beatae vitae
+dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
+aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
+eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam
+est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
+velit, sed quia non numquam eius modi tempora incidunt ut labore
+et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima
+veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,
+nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
+reprehenderit qui in ea voluptate velit esse quam nihil molestiae
+consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla
+pariatur?
+
+At vero eos et accusamus et iusto odio dignissimos ducimus qui
+blanditiis praesentium voluptatum deleniti atque corrupti quos
+dolores et quas molestias excepturi sint occaecati cupiditate non
+provident, similique sunt in culpa qui officia deserunt mollitia
+animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis
+est et expedita distinctio. Nam libero tempore, cum soluta nobis
+est eligendi optio cumque nihil impedit quo minus id quod maxime
+placeat facere possimus, omnis voluptas assumenda est, omnis dolor
+repellendus. Temporibus autem quibusdam et aut officiis debitis aut
+rerum necessitatibus saepe eveniet ut et voluptates repudiandae
+sint et molestiae non recusandae. Itaque earum rerum hic tenetur a
+sapiente delectus, ut aut reiciendis voluptatibus maiores alias
+consequatur aut perferendis doloribus asperiores repellat.
diff --git a/src/cleanup/test-queue-file b/src/cleanup/test-queue-file
new file mode 100644
index 0000000..8412ae3
--- /dev/null
+++ b/src/cleanup/test-queue-file
Binary files differ
diff --git a/src/cleanup/test-queue-file10 b/src/cleanup/test-queue-file10
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file10
Binary files differ
diff --git a/src/cleanup/test-queue-file11 b/src/cleanup/test-queue-file11
new file mode 100644
index 0000000..745dc90
--- /dev/null
+++ b/src/cleanup/test-queue-file11
Binary files differ
diff --git a/src/cleanup/test-queue-file12 b/src/cleanup/test-queue-file12
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file12
Binary files differ
diff --git a/src/cleanup/test-queue-file13a b/src/cleanup/test-queue-file13a
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13a
Binary files differ
diff --git a/src/cleanup/test-queue-file13b b/src/cleanup/test-queue-file13b
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13b
Binary files differ
diff --git a/src/cleanup/test-queue-file13c b/src/cleanup/test-queue-file13c
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13c
Binary files differ
diff --git a/src/cleanup/test-queue-file13d b/src/cleanup/test-queue-file13d
new file mode 100644
index 0000000..745dc90
--- /dev/null
+++ b/src/cleanup/test-queue-file13d
Binary files differ
diff --git a/src/cleanup/test-queue-file13e b/src/cleanup/test-queue-file13e
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13e
Binary files differ
diff --git a/src/cleanup/test-queue-file13f b/src/cleanup/test-queue-file13f
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13f
Binary files differ
diff --git a/src/cleanup/test-queue-file13g b/src/cleanup/test-queue-file13g
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13g
Binary files differ
diff --git a/src/cleanup/test-queue-file13h b/src/cleanup/test-queue-file13h
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13h
Binary files differ
diff --git a/src/cleanup/test-queue-file13i b/src/cleanup/test-queue-file13i
new file mode 100644
index 0000000..4979c1d
--- /dev/null
+++ b/src/cleanup/test-queue-file13i
Binary files differ
diff --git a/src/cleanup/test-queue-file14 b/src/cleanup/test-queue-file14
new file mode 100644
index 0000000..e9b388a
--- /dev/null
+++ b/src/cleanup/test-queue-file14
Binary files differ
diff --git a/src/cleanup/test-queue-file15 b/src/cleanup/test-queue-file15
new file mode 100644
index 0000000..088bdfd
--- /dev/null
+++ b/src/cleanup/test-queue-file15
Binary files differ
diff --git a/src/cleanup/test-queue-file16 b/src/cleanup/test-queue-file16
new file mode 100644
index 0000000..3f7c5f3
--- /dev/null
+++ b/src/cleanup/test-queue-file16
Binary files differ
diff --git a/src/cleanup/test-queue-file2 b/src/cleanup/test-queue-file2
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file2
Binary files differ
diff --git a/src/cleanup/test-queue-file3 b/src/cleanup/test-queue-file3
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file3
Binary files differ
diff --git a/src/cleanup/test-queue-file4 b/src/cleanup/test-queue-file4
new file mode 100644
index 0000000..8412ae3
--- /dev/null
+++ b/src/cleanup/test-queue-file4
Binary files differ
diff --git a/src/cleanup/test-queue-file5 b/src/cleanup/test-queue-file5
new file mode 100644
index 0000000..d7adfb4
--- /dev/null
+++ b/src/cleanup/test-queue-file5
Binary files differ
diff --git a/src/cleanup/test-queue-file6 b/src/cleanup/test-queue-file6
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file6
Binary files differ
diff --git a/src/cleanup/test-queue-file7 b/src/cleanup/test-queue-file7
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file7
Binary files differ
diff --git a/src/cleanup/test-queue-file8 b/src/cleanup/test-queue-file8
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file8
Binary files differ
diff --git a/src/cleanup/test-queue-file9 b/src/cleanup/test-queue-file9
new file mode 100644
index 0000000..27a9ec7
--- /dev/null
+++ b/src/cleanup/test-queue-file9
Binary files differ